mirror of
https://github.com/home-assistant/core.git
synced 2025-07-28 15:47:12 +00:00
Move HassIntent handler code into helpers/intent (#12181)
* Moved TurnOn/Off Intents to component * Removed unused import * Lint fix which my local runs dont catch apparently... * Moved hass intent code into intent * Added test for toggle to conversation. * Fixed toggle tests * Update intent.py * Added homeassistant.helpers to gen_requirements script. * Update intent.py * Update intent.py * Changed return value for _match_entity * Moved consts and requirements * Removed unused import * Removed http view * Removed http import * Removed fuzzywuzzy dependency * woof * A few cleanups * Added domain filtering to entities * Clarified class doc string * Added doc string * Added test in test_init * woof * Cleanup entity matching * Update intent.py * removed uneeded setup from tests
This commit is contained in:
parent
678f284015
commit
26209de2f2
@ -15,6 +15,7 @@ import homeassistant.core as ha
|
|||||||
import homeassistant.config as conf_util
|
import homeassistant.config as conf_util
|
||||||
from homeassistant.exceptions import HomeAssistantError
|
from homeassistant.exceptions import HomeAssistantError
|
||||||
from homeassistant.helpers.service import extract_entity_ids
|
from homeassistant.helpers.service import extract_entity_ids
|
||||||
|
from homeassistant.helpers import intent
|
||||||
from homeassistant.const import (
|
from homeassistant.const import (
|
||||||
ATTR_ENTITY_ID, SERVICE_TURN_ON, SERVICE_TURN_OFF, SERVICE_TOGGLE,
|
ATTR_ENTITY_ID, SERVICE_TURN_ON, SERVICE_TURN_OFF, SERVICE_TOGGLE,
|
||||||
SERVICE_HOMEASSISTANT_STOP, SERVICE_HOMEASSISTANT_RESTART,
|
SERVICE_HOMEASSISTANT_STOP, SERVICE_HOMEASSISTANT_RESTART,
|
||||||
@ -154,6 +155,12 @@ def async_setup(hass, config):
|
|||||||
ha.DOMAIN, SERVICE_TURN_ON, async_handle_turn_service)
|
ha.DOMAIN, SERVICE_TURN_ON, async_handle_turn_service)
|
||||||
hass.services.async_register(
|
hass.services.async_register(
|
||||||
ha.DOMAIN, SERVICE_TOGGLE, async_handle_turn_service)
|
ha.DOMAIN, SERVICE_TOGGLE, async_handle_turn_service)
|
||||||
|
hass.helpers.intent.async_register(intent.ServiceIntentHandler(
|
||||||
|
intent.INTENT_TURN_ON, ha.DOMAIN, SERVICE_TURN_ON, "Turned on {}"))
|
||||||
|
hass.helpers.intent.async_register(intent.ServiceIntentHandler(
|
||||||
|
intent.INTENT_TURN_OFF, ha.DOMAIN, SERVICE_TURN_OFF, "Turned off {}"))
|
||||||
|
hass.helpers.intent.async_register(intent.ServiceIntentHandler(
|
||||||
|
intent.INTENT_TOGGLE, ha.DOMAIN, SERVICE_TOGGLE, "Toggled {}"))
|
||||||
|
|
||||||
@asyncio.coroutine
|
@asyncio.coroutine
|
||||||
def async_handle_core_service(call):
|
def async_handle_core_service(call):
|
||||||
|
@ -7,19 +7,15 @@ https://home-assistant.io/components/conversation/
|
|||||||
import asyncio
|
import asyncio
|
||||||
import logging
|
import logging
|
||||||
import re
|
import re
|
||||||
import warnings
|
|
||||||
|
|
||||||
import voluptuous as vol
|
import voluptuous as vol
|
||||||
|
|
||||||
from homeassistant import core
|
from homeassistant import core
|
||||||
from homeassistant.components import http
|
from homeassistant.components import http
|
||||||
from homeassistant.const import (
|
|
||||||
ATTR_ENTITY_ID, SERVICE_TURN_OFF, SERVICE_TURN_ON)
|
|
||||||
from homeassistant.helpers import config_validation as cv
|
from homeassistant.helpers import config_validation as cv
|
||||||
from homeassistant.helpers import intent
|
from homeassistant.helpers import intent
|
||||||
from homeassistant.loader import bind_hass
|
|
||||||
|
|
||||||
REQUIREMENTS = ['fuzzywuzzy==0.16.0']
|
from homeassistant.loader import bind_hass
|
||||||
|
|
||||||
_LOGGER = logging.getLogger(__name__)
|
_LOGGER = logging.getLogger(__name__)
|
||||||
|
|
||||||
@ -28,9 +24,6 @@ ATTR_TEXT = 'text'
|
|||||||
DEPENDENCIES = ['http']
|
DEPENDENCIES = ['http']
|
||||||
DOMAIN = 'conversation'
|
DOMAIN = 'conversation'
|
||||||
|
|
||||||
INTENT_TURN_OFF = 'HassTurnOff'
|
|
||||||
INTENT_TURN_ON = 'HassTurnOn'
|
|
||||||
|
|
||||||
REGEX_TURN_COMMAND = re.compile(r'turn (?P<name>(?: |\w)+) (?P<command>\w+)')
|
REGEX_TURN_COMMAND = re.compile(r'turn (?P<name>(?: |\w)+) (?P<command>\w+)')
|
||||||
REGEX_TYPE = type(re.compile(''))
|
REGEX_TYPE = type(re.compile(''))
|
||||||
|
|
||||||
@ -50,7 +43,7 @@ CONFIG_SCHEMA = vol.Schema({DOMAIN: vol.Schema({
|
|||||||
@core.callback
|
@core.callback
|
||||||
@bind_hass
|
@bind_hass
|
||||||
def async_register(hass, intent_type, utterances):
|
def async_register(hass, intent_type, utterances):
|
||||||
"""Register an intent.
|
"""Register utterances and any custom intents.
|
||||||
|
|
||||||
Registrations don't require conversations to be loaded. They will become
|
Registrations don't require conversations to be loaded. They will become
|
||||||
active once the conversation component is loaded.
|
active once the conversation component is loaded.
|
||||||
@ -75,8 +68,6 @@ def async_register(hass, intent_type, utterances):
|
|||||||
@asyncio.coroutine
|
@asyncio.coroutine
|
||||||
def async_setup(hass, config):
|
def async_setup(hass, config):
|
||||||
"""Register the process service."""
|
"""Register the process service."""
|
||||||
warnings.filterwarnings('ignore', module='fuzzywuzzy')
|
|
||||||
|
|
||||||
config = config.get(DOMAIN, {})
|
config = config.get(DOMAIN, {})
|
||||||
intents = hass.data.get(DOMAIN)
|
intents = hass.data.get(DOMAIN)
|
||||||
|
|
||||||
@ -102,12 +93,12 @@ def async_setup(hass, config):
|
|||||||
|
|
||||||
hass.http.register_view(ConversationProcessView)
|
hass.http.register_view(ConversationProcessView)
|
||||||
|
|
||||||
hass.helpers.intent.async_register(TurnOnIntent())
|
async_register(hass, intent.INTENT_TURN_ON,
|
||||||
hass.helpers.intent.async_register(TurnOffIntent())
|
|
||||||
async_register(hass, INTENT_TURN_ON,
|
|
||||||
['Turn {name} on', 'Turn on {name}'])
|
['Turn {name} on', 'Turn on {name}'])
|
||||||
async_register(hass, INTENT_TURN_OFF, [
|
async_register(hass, intent.INTENT_TURN_OFF,
|
||||||
'Turn {name} off', 'Turn off {name}'])
|
['Turn {name} off', 'Turn off {name}'])
|
||||||
|
async_register(hass, intent.INTENT_TOGGLE,
|
||||||
|
['Toggle {name}', '{name} toggle'])
|
||||||
|
|
||||||
return True
|
return True
|
||||||
|
|
||||||
@ -151,79 +142,6 @@ def _process(hass, text):
|
|||||||
return response
|
return response
|
||||||
|
|
||||||
|
|
||||||
@core.callback
|
|
||||||
def _match_entity(hass, name):
|
|
||||||
"""Match a name to an entity."""
|
|
||||||
from fuzzywuzzy import process as fuzzyExtract
|
|
||||||
entities = {state.entity_id: state.name for state
|
|
||||||
in hass.states.async_all()}
|
|
||||||
entity_id = fuzzyExtract.extractOne(
|
|
||||||
name, entities, score_cutoff=65)[2]
|
|
||||||
return hass.states.get(entity_id) if entity_id else None
|
|
||||||
|
|
||||||
|
|
||||||
class TurnOnIntent(intent.IntentHandler):
|
|
||||||
"""Handle turning item on intents."""
|
|
||||||
|
|
||||||
intent_type = INTENT_TURN_ON
|
|
||||||
slot_schema = {
|
|
||||||
'name': cv.string,
|
|
||||||
}
|
|
||||||
|
|
||||||
@asyncio.coroutine
|
|
||||||
def async_handle(self, intent_obj):
|
|
||||||
"""Handle turn on intent."""
|
|
||||||
hass = intent_obj.hass
|
|
||||||
slots = self.async_validate_slots(intent_obj.slots)
|
|
||||||
name = slots['name']['value']
|
|
||||||
entity = _match_entity(hass, name)
|
|
||||||
|
|
||||||
if not entity:
|
|
||||||
_LOGGER.error("Could not find entity id for %s", name)
|
|
||||||
return None
|
|
||||||
|
|
||||||
yield from hass.services.async_call(
|
|
||||||
core.DOMAIN, SERVICE_TURN_ON, {
|
|
||||||
ATTR_ENTITY_ID: entity.entity_id,
|
|
||||||
}, blocking=True)
|
|
||||||
|
|
||||||
response = intent_obj.create_response()
|
|
||||||
response.async_set_speech(
|
|
||||||
'Turned on {}'.format(entity.name))
|
|
||||||
return response
|
|
||||||
|
|
||||||
|
|
||||||
class TurnOffIntent(intent.IntentHandler):
|
|
||||||
"""Handle turning item off intents."""
|
|
||||||
|
|
||||||
intent_type = INTENT_TURN_OFF
|
|
||||||
slot_schema = {
|
|
||||||
'name': cv.string,
|
|
||||||
}
|
|
||||||
|
|
||||||
@asyncio.coroutine
|
|
||||||
def async_handle(self, intent_obj):
|
|
||||||
"""Handle turn off intent."""
|
|
||||||
hass = intent_obj.hass
|
|
||||||
slots = self.async_validate_slots(intent_obj.slots)
|
|
||||||
name = slots['name']['value']
|
|
||||||
entity = _match_entity(hass, name)
|
|
||||||
|
|
||||||
if not entity:
|
|
||||||
_LOGGER.error("Could not find entity id for %s", name)
|
|
||||||
return None
|
|
||||||
|
|
||||||
yield from hass.services.async_call(
|
|
||||||
core.DOMAIN, SERVICE_TURN_OFF, {
|
|
||||||
ATTR_ENTITY_ID: entity.entity_id,
|
|
||||||
}, blocking=True)
|
|
||||||
|
|
||||||
response = intent_obj.create_response()
|
|
||||||
response.async_set_speech(
|
|
||||||
'Turned off {}'.format(entity.name))
|
|
||||||
return response
|
|
||||||
|
|
||||||
|
|
||||||
class ConversationProcessView(http.HomeAssistantView):
|
class ConversationProcessView(http.HomeAssistantView):
|
||||||
"""View to retrieve shopping list content."""
|
"""View to retrieve shopping list content."""
|
||||||
|
|
||||||
|
@ -1,20 +1,27 @@
|
|||||||
"""Module to coordinate user intentions."""
|
"""Module to coordinate user intentions."""
|
||||||
import asyncio
|
import asyncio
|
||||||
import logging
|
import logging
|
||||||
|
import re
|
||||||
|
|
||||||
import voluptuous as vol
|
import voluptuous as vol
|
||||||
|
|
||||||
from homeassistant.core import callback
|
from homeassistant.core import callback
|
||||||
from homeassistant.exceptions import HomeAssistantError
|
from homeassistant.exceptions import HomeAssistantError
|
||||||
|
from homeassistant.helpers import config_validation as cv
|
||||||
from homeassistant.loader import bind_hass
|
from homeassistant.loader import bind_hass
|
||||||
|
from homeassistant.const import ATTR_ENTITY_ID
|
||||||
|
|
||||||
|
|
||||||
DATA_KEY = 'intent'
|
|
||||||
_LOGGER = logging.getLogger(__name__)
|
_LOGGER = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
INTENT_TURN_OFF = 'HassTurnOff'
|
||||||
|
INTENT_TURN_ON = 'HassTurnOn'
|
||||||
|
INTENT_TOGGLE = 'HassToggle'
|
||||||
|
|
||||||
SLOT_SCHEMA = vol.Schema({
|
SLOT_SCHEMA = vol.Schema({
|
||||||
}, extra=vol.ALLOW_EXTRA)
|
}, extra=vol.ALLOW_EXTRA)
|
||||||
|
|
||||||
|
DATA_KEY = 'intent'
|
||||||
|
|
||||||
SPEECH_TYPE_PLAIN = 'plain'
|
SPEECH_TYPE_PLAIN = 'plain'
|
||||||
SPEECH_TYPE_SSML = 'ssml'
|
SPEECH_TYPE_SSML = 'ssml'
|
||||||
|
|
||||||
@ -87,7 +94,7 @@ class IntentHandler:
|
|||||||
intent_type = None
|
intent_type = None
|
||||||
slot_schema = None
|
slot_schema = None
|
||||||
_slot_schema = None
|
_slot_schema = None
|
||||||
platforms = None
|
platforms = []
|
||||||
|
|
||||||
@callback
|
@callback
|
||||||
def async_can_handle(self, intent_obj):
|
def async_can_handle(self, intent_obj):
|
||||||
@ -117,6 +124,67 @@ class IntentHandler:
|
|||||||
return '<{} - {}>'.format(self.__class__.__name__, self.intent_type)
|
return '<{} - {}>'.format(self.__class__.__name__, self.intent_type)
|
||||||
|
|
||||||
|
|
||||||
|
def fuzzymatch(name, entities):
|
||||||
|
"""Fuzzy matching function."""
|
||||||
|
matches = []
|
||||||
|
pattern = '.*?'.join(name)
|
||||||
|
regex = re.compile(pattern, re.IGNORECASE)
|
||||||
|
for entity_id, entity_name in entities.items():
|
||||||
|
match = regex.search(entity_name)
|
||||||
|
if match:
|
||||||
|
matches.append((len(match.group()), match.start(), entity_id))
|
||||||
|
return [x for _, _, x in sorted(matches)]
|
||||||
|
|
||||||
|
|
||||||
|
class ServiceIntentHandler(IntentHandler):
|
||||||
|
"""Service Intent handler registration.
|
||||||
|
|
||||||
|
Service specific intent handler that calls a service by name/entity_id.
|
||||||
|
"""
|
||||||
|
|
||||||
|
slot_schema = {
|
||||||
|
'name': cv.string,
|
||||||
|
}
|
||||||
|
|
||||||
|
def __init__(self, intent_type, domain, service, speech):
|
||||||
|
"""Create Service Intent Handler."""
|
||||||
|
self.intent_type = intent_type
|
||||||
|
self.domain = domain
|
||||||
|
self.service = service
|
||||||
|
self.speech = speech
|
||||||
|
|
||||||
|
@asyncio.coroutine
|
||||||
|
def async_handle(self, intent_obj):
|
||||||
|
"""Handle the hass intent."""
|
||||||
|
hass = intent_obj.hass
|
||||||
|
slots = self.async_validate_slots(intent_obj.slots)
|
||||||
|
response = intent_obj.create_response()
|
||||||
|
|
||||||
|
name = slots['name']['value']
|
||||||
|
entities = {state.entity_id: state.name for state
|
||||||
|
in hass.states.async_all()}
|
||||||
|
|
||||||
|
matches = fuzzymatch(name, entities)
|
||||||
|
entity_id = matches[0] if matches else None
|
||||||
|
_LOGGER.debug("%s matched entity: %s", name, entity_id)
|
||||||
|
|
||||||
|
response = intent_obj.create_response()
|
||||||
|
if not entity_id:
|
||||||
|
response.async_set_speech(
|
||||||
|
"Could not find entity id matching {}.".format(name))
|
||||||
|
_LOGGER.error("Could not find entity id matching %s", name)
|
||||||
|
return response
|
||||||
|
|
||||||
|
yield from hass.services.async_call(
|
||||||
|
self.domain, self.service, {
|
||||||
|
ATTR_ENTITY_ID: entity_id
|
||||||
|
})
|
||||||
|
|
||||||
|
response.async_set_speech(
|
||||||
|
self.speech.format(name))
|
||||||
|
return response
|
||||||
|
|
||||||
|
|
||||||
class Intent:
|
class Intent:
|
||||||
"""Hold the intent."""
|
"""Hold the intent."""
|
||||||
|
|
||||||
|
@ -295,9 +295,6 @@ fritzhome==1.0.4
|
|||||||
# homeassistant.components.media_player.frontier_silicon
|
# homeassistant.components.media_player.frontier_silicon
|
||||||
fsapi==0.0.7
|
fsapi==0.0.7
|
||||||
|
|
||||||
# homeassistant.components.conversation
|
|
||||||
fuzzywuzzy==0.16.0
|
|
||||||
|
|
||||||
# homeassistant.components.tts.google
|
# homeassistant.components.tts.google
|
||||||
gTTS-token==1.1.1
|
gTTS-token==1.1.1
|
||||||
|
|
||||||
|
@ -56,9 +56,6 @@ evohomeclient==0.2.5
|
|||||||
# homeassistant.components.sensor.geo_rss_events
|
# homeassistant.components.sensor.geo_rss_events
|
||||||
feedparser==5.2.1
|
feedparser==5.2.1
|
||||||
|
|
||||||
# homeassistant.components.conversation
|
|
||||||
fuzzywuzzy==0.16.0
|
|
||||||
|
|
||||||
# homeassistant.components.tts.google
|
# homeassistant.components.tts.google
|
||||||
gTTS-token==1.1.1
|
gTTS-token==1.1.1
|
||||||
|
|
||||||
|
@ -46,7 +46,6 @@ TEST_REQUIREMENTS = (
|
|||||||
'ephem',
|
'ephem',
|
||||||
'evohomeclient',
|
'evohomeclient',
|
||||||
'feedparser',
|
'feedparser',
|
||||||
'fuzzywuzzy',
|
|
||||||
'gTTS-token',
|
'gTTS-token',
|
||||||
'ha-ffmpeg',
|
'ha-ffmpeg',
|
||||||
'haversine',
|
'haversine',
|
||||||
|
@ -6,6 +6,7 @@ import pytest
|
|||||||
|
|
||||||
from homeassistant.setup import async_setup_component
|
from homeassistant.setup import async_setup_component
|
||||||
from homeassistant.components import conversation
|
from homeassistant.components import conversation
|
||||||
|
import homeassistant.components as component
|
||||||
from homeassistant.helpers import intent
|
from homeassistant.helpers import intent
|
||||||
|
|
||||||
from tests.common import async_mock_intent, async_mock_service
|
from tests.common import async_mock_intent, async_mock_service
|
||||||
@ -16,6 +17,9 @@ def test_calling_intent(hass):
|
|||||||
"""Test calling an intent from a conversation."""
|
"""Test calling an intent from a conversation."""
|
||||||
intents = async_mock_intent(hass, 'OrderBeer')
|
intents = async_mock_intent(hass, 'OrderBeer')
|
||||||
|
|
||||||
|
result = yield from component.async_setup(hass, {})
|
||||||
|
assert result
|
||||||
|
|
||||||
result = yield from async_setup_component(hass, 'conversation', {
|
result = yield from async_setup_component(hass, 'conversation', {
|
||||||
'conversation': {
|
'conversation': {
|
||||||
'intents': {
|
'intents': {
|
||||||
@ -145,6 +149,9 @@ def test_http_processing_intent(hass, test_client):
|
|||||||
@pytest.mark.parametrize('sentence', ('turn on kitchen', 'turn kitchen on'))
|
@pytest.mark.parametrize('sentence', ('turn on kitchen', 'turn kitchen on'))
|
||||||
def test_turn_on_intent(hass, sentence):
|
def test_turn_on_intent(hass, sentence):
|
||||||
"""Test calling the turn on intent."""
|
"""Test calling the turn on intent."""
|
||||||
|
result = yield from component.async_setup(hass, {})
|
||||||
|
assert result
|
||||||
|
|
||||||
result = yield from async_setup_component(hass, 'conversation', {})
|
result = yield from async_setup_component(hass, 'conversation', {})
|
||||||
assert result
|
assert result
|
||||||
|
|
||||||
@ -168,6 +175,9 @@ def test_turn_on_intent(hass, sentence):
|
|||||||
@pytest.mark.parametrize('sentence', ('turn off kitchen', 'turn kitchen off'))
|
@pytest.mark.parametrize('sentence', ('turn off kitchen', 'turn kitchen off'))
|
||||||
def test_turn_off_intent(hass, sentence):
|
def test_turn_off_intent(hass, sentence):
|
||||||
"""Test calling the turn on intent."""
|
"""Test calling the turn on intent."""
|
||||||
|
result = yield from component.async_setup(hass, {})
|
||||||
|
assert result
|
||||||
|
|
||||||
result = yield from async_setup_component(hass, 'conversation', {})
|
result = yield from async_setup_component(hass, 'conversation', {})
|
||||||
assert result
|
assert result
|
||||||
|
|
||||||
@ -187,9 +197,38 @@ def test_turn_off_intent(hass, sentence):
|
|||||||
assert call.data == {'entity_id': 'light.kitchen'}
|
assert call.data == {'entity_id': 'light.kitchen'}
|
||||||
|
|
||||||
|
|
||||||
|
@asyncio.coroutine
|
||||||
|
@pytest.mark.parametrize('sentence', ('toggle kitchen', 'kitchen toggle'))
|
||||||
|
def test_toggle_intent(hass, sentence):
|
||||||
|
"""Test calling the turn on intent."""
|
||||||
|
result = yield from component.async_setup(hass, {})
|
||||||
|
assert result
|
||||||
|
|
||||||
|
result = yield from async_setup_component(hass, 'conversation', {})
|
||||||
|
assert result
|
||||||
|
|
||||||
|
hass.states.async_set('light.kitchen', 'on')
|
||||||
|
calls = async_mock_service(hass, 'homeassistant', 'toggle')
|
||||||
|
|
||||||
|
yield from hass.services.async_call(
|
||||||
|
'conversation', 'process', {
|
||||||
|
conversation.ATTR_TEXT: sentence
|
||||||
|
})
|
||||||
|
yield from hass.async_block_till_done()
|
||||||
|
|
||||||
|
assert len(calls) == 1
|
||||||
|
call = calls[0]
|
||||||
|
assert call.domain == 'homeassistant'
|
||||||
|
assert call.service == 'toggle'
|
||||||
|
assert call.data == {'entity_id': 'light.kitchen'}
|
||||||
|
|
||||||
|
|
||||||
@asyncio.coroutine
|
@asyncio.coroutine
|
||||||
def test_http_api(hass, test_client):
|
def test_http_api(hass, test_client):
|
||||||
"""Test the HTTP conversation API."""
|
"""Test the HTTP conversation API."""
|
||||||
|
result = yield from component.async_setup(hass, {})
|
||||||
|
assert result
|
||||||
|
|
||||||
result = yield from async_setup_component(hass, 'conversation', {})
|
result = yield from async_setup_component(hass, 'conversation', {})
|
||||||
assert result
|
assert result
|
||||||
|
|
||||||
@ -212,6 +251,9 @@ def test_http_api(hass, test_client):
|
|||||||
@asyncio.coroutine
|
@asyncio.coroutine
|
||||||
def test_http_api_wrong_data(hass, test_client):
|
def test_http_api_wrong_data(hass, test_client):
|
||||||
"""Test the HTTP conversation API."""
|
"""Test the HTTP conversation API."""
|
||||||
|
result = yield from component.async_setup(hass, {})
|
||||||
|
assert result
|
||||||
|
|
||||||
result = yield from async_setup_component(hass, 'conversation', {})
|
result = yield from async_setup_component(hass, 'conversation', {})
|
||||||
assert result
|
assert result
|
||||||
|
|
||||||
|
@ -11,6 +11,7 @@ from homeassistant import config
|
|||||||
from homeassistant.const import (
|
from homeassistant.const import (
|
||||||
STATE_ON, STATE_OFF, SERVICE_TURN_ON, SERVICE_TURN_OFF, SERVICE_TOGGLE)
|
STATE_ON, STATE_OFF, SERVICE_TURN_ON, SERVICE_TURN_OFF, SERVICE_TOGGLE)
|
||||||
import homeassistant.components as comps
|
import homeassistant.components as comps
|
||||||
|
import homeassistant.helpers.intent as intent
|
||||||
from homeassistant.exceptions import HomeAssistantError
|
from homeassistant.exceptions import HomeAssistantError
|
||||||
from homeassistant.helpers import entity
|
from homeassistant.helpers import entity
|
||||||
from homeassistant.util.async import run_coroutine_threadsafe
|
from homeassistant.util.async import run_coroutine_threadsafe
|
||||||
@ -195,3 +196,96 @@ class TestComponentsCore(unittest.TestCase):
|
|||||||
self.hass.block_till_done()
|
self.hass.block_till_done()
|
||||||
assert mock_check.called
|
assert mock_check.called
|
||||||
assert not mock_stop.called
|
assert not mock_stop.called
|
||||||
|
|
||||||
|
|
||||||
|
@asyncio.coroutine
|
||||||
|
def test_turn_on_intent(hass):
|
||||||
|
"""Test HassTurnOn intent."""
|
||||||
|
result = yield from comps.async_setup(hass, {})
|
||||||
|
assert result
|
||||||
|
|
||||||
|
hass.states.async_set('light.test_light', 'off')
|
||||||
|
calls = async_mock_service(hass, 'light', SERVICE_TURN_ON)
|
||||||
|
|
||||||
|
response = yield from intent.async_handle(
|
||||||
|
hass, 'test', 'HassTurnOn', {'name': {'value': 'test light'}}
|
||||||
|
)
|
||||||
|
yield from hass.async_block_till_done()
|
||||||
|
|
||||||
|
assert response.speech['plain']['speech'] == 'Turned on test light'
|
||||||
|
assert len(calls) == 1
|
||||||
|
call = calls[0]
|
||||||
|
assert call.domain == 'light'
|
||||||
|
assert call.service == 'turn_on'
|
||||||
|
assert call.data == {'entity_id': ['light.test_light']}
|
||||||
|
|
||||||
|
|
||||||
|
@asyncio.coroutine
|
||||||
|
def test_turn_off_intent(hass):
|
||||||
|
"""Test HassTurnOff intent."""
|
||||||
|
result = yield from comps.async_setup(hass, {})
|
||||||
|
assert result
|
||||||
|
|
||||||
|
hass.states.async_set('light.test_light', 'on')
|
||||||
|
calls = async_mock_service(hass, 'light', SERVICE_TURN_OFF)
|
||||||
|
|
||||||
|
response = yield from intent.async_handle(
|
||||||
|
hass, 'test', 'HassTurnOff', {'name': {'value': 'test light'}}
|
||||||
|
)
|
||||||
|
yield from hass.async_block_till_done()
|
||||||
|
|
||||||
|
assert response.speech['plain']['speech'] == 'Turned off test light'
|
||||||
|
assert len(calls) == 1
|
||||||
|
call = calls[0]
|
||||||
|
assert call.domain == 'light'
|
||||||
|
assert call.service == 'turn_off'
|
||||||
|
assert call.data == {'entity_id': ['light.test_light']}
|
||||||
|
|
||||||
|
|
||||||
|
@asyncio.coroutine
|
||||||
|
def test_toggle_intent(hass):
|
||||||
|
"""Test HassToggle intent."""
|
||||||
|
result = yield from comps.async_setup(hass, {})
|
||||||
|
assert result
|
||||||
|
|
||||||
|
hass.states.async_set('light.test_light', 'off')
|
||||||
|
calls = async_mock_service(hass, 'light', SERVICE_TOGGLE)
|
||||||
|
|
||||||
|
response = yield from intent.async_handle(
|
||||||
|
hass, 'test', 'HassToggle', {'name': {'value': 'test light'}}
|
||||||
|
)
|
||||||
|
yield from hass.async_block_till_done()
|
||||||
|
|
||||||
|
assert response.speech['plain']['speech'] == 'Toggled test light'
|
||||||
|
assert len(calls) == 1
|
||||||
|
call = calls[0]
|
||||||
|
assert call.domain == 'light'
|
||||||
|
assert call.service == 'toggle'
|
||||||
|
assert call.data == {'entity_id': ['light.test_light']}
|
||||||
|
|
||||||
|
|
||||||
|
@asyncio.coroutine
|
||||||
|
def test_turn_on_multiple_intent(hass):
|
||||||
|
"""Test HassTurnOn intent with multiple similar entities.
|
||||||
|
|
||||||
|
This tests that matching finds the proper entity among similar names.
|
||||||
|
"""
|
||||||
|
result = yield from comps.async_setup(hass, {})
|
||||||
|
assert result
|
||||||
|
|
||||||
|
hass.states.async_set('light.test_light', 'off')
|
||||||
|
hass.states.async_set('light.test_lights_2', 'off')
|
||||||
|
hass.states.async_set('light.test_lighter', 'off')
|
||||||
|
calls = async_mock_service(hass, 'light', SERVICE_TURN_ON)
|
||||||
|
|
||||||
|
response = yield from intent.async_handle(
|
||||||
|
hass, 'test', 'HassTurnOn', {'name': {'value': 'test lights'}}
|
||||||
|
)
|
||||||
|
yield from hass.async_block_till_done()
|
||||||
|
|
||||||
|
assert response.speech['plain']['speech'] == 'Turned on test lights'
|
||||||
|
assert len(calls) == 1
|
||||||
|
call = calls[0]
|
||||||
|
assert call.domain == 'light'
|
||||||
|
assert call.service == 'turn_on'
|
||||||
|
assert call.data == {'entity_id': ['light.test_lights_2']}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user