From eedb3d0c08b8cf151d288ef26d034947a1676651 Mon Sep 17 00:00:00 2001 From: tschmidty69 Date: Sat, 3 Feb 2018 10:52:49 -0500 Subject: [PATCH 01/26] Moved TurnOn/Off Intents to component --- homeassistant/components/__init__.py | 84 +++++++++++++++++++++++- homeassistant/components/conversation.py | 84 +----------------------- homeassistant/const.py | 4 ++ tests/components/test_conversation.py | 23 +++++++ 4 files changed, 111 insertions(+), 84 deletions(-) diff --git a/homeassistant/components/__init__.py b/homeassistant/components/__init__.py index 6db147a5f59323..1723be11855e6a 100644 --- a/homeassistant/components/__init__.py +++ b/homeassistant/components/__init__.py @@ -14,18 +14,22 @@ import homeassistant.core as ha import homeassistant.config as conf_util from homeassistant.exceptions import HomeAssistantError +from homeassistant.helpers import config_validation as cv from homeassistant.helpers.service import extract_entity_ids +from homeassistant.helpers import intent from homeassistant.const import ( - ATTR_ENTITY_ID, SERVICE_TURN_ON, SERVICE_TURN_OFF, SERVICE_TOGGLE, + ATTR_ENTITY_ID, INTENT_TURN_ON, INTENT_TURN_OFF, + SERVICE_TURN_ON, SERVICE_TURN_OFF, SERVICE_TOGGLE, SERVICE_HOMEASSISTANT_STOP, SERVICE_HOMEASSISTANT_RESTART, RESTART_EXIT_CODE) +REQUIREMENTS = ['fuzzywuzzy==0.16.0'] + _LOGGER = logging.getLogger(__name__) SERVICE_RELOAD_CORE_CONFIG = 'reload_core_config' SERVICE_CHECK_CONFIG = 'check_config' - def is_on(hass, entity_id=None): """Load up the module to call the is_on method. @@ -154,6 +158,9 @@ def async_handle_turn_service(service): ha.DOMAIN, SERVICE_TURN_ON, async_handle_turn_service) hass.services.async_register( ha.DOMAIN, SERVICE_TOGGLE, async_handle_turn_service) + hass.helpers.intent.async_register(TurnOnIntent()) + hass.helpers.intent.async_register(TurnOffIntent()) + @asyncio.coroutine def async_handle_core_service(call): @@ -200,3 +207,76 @@ def async_handle_reload_config(call): ha.DOMAIN, SERVICE_RELOAD_CORE_CONFIG, async_handle_reload_config) return True + + +@ha.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( + ha.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( + ha.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 diff --git a/homeassistant/components/conversation.py b/homeassistant/components/conversation.py index 5187b4782ef4d4..463fec820952fb 100644 --- a/homeassistant/components/conversation.py +++ b/homeassistant/components/conversation.py @@ -13,8 +13,7 @@ from homeassistant import core from homeassistant.components import http -from homeassistant.const import ( - ATTR_ENTITY_ID, SERVICE_TURN_OFF, SERVICE_TURN_ON) +from homeassistant.const import INTENT_TURN_ON, INTENT_TURN_OFF from homeassistant.helpers import config_validation as cv from homeassistant.helpers import intent from homeassistant.loader import bind_hass @@ -28,9 +27,6 @@ DEPENDENCIES = ['http'] DOMAIN = 'conversation' -INTENT_TURN_OFF = 'HassTurnOff' -INTENT_TURN_ON = 'HassTurnOn' - REGEX_TURN_COMMAND = re.compile(r'turn (?P(?: |\w)+) (?P\w+)') REGEX_TYPE = type(re.compile('')) @@ -50,7 +46,7 @@ @core.callback @bind_hass 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 active once the conversation component is loaded. @@ -75,7 +71,6 @@ def async_register(hass, intent_type, utterances): @asyncio.coroutine def async_setup(hass, config): """Register the process service.""" - warnings.filterwarnings('ignore', module='fuzzywuzzy') config = config.get(DOMAIN, {}) intents = hass.data.get(DOMAIN) @@ -102,8 +97,6 @@ def process(service): hass.http.register_view(ConversationProcessView) - hass.helpers.intent.async_register(TurnOnIntent()) - hass.helpers.intent.async_register(TurnOffIntent()) async_register(hass, INTENT_TURN_ON, ['Turn {name} on', 'Turn on {name}']) async_register(hass, INTENT_TURN_OFF, [ @@ -151,79 +144,6 @@ def _process(hass, text): 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): """View to retrieve shopping list content.""" diff --git a/homeassistant/const.py b/homeassistant/const.py index 560c99bb653bca..d335e2814a5414 100644 --- a/homeassistant/const.py +++ b/homeassistant/const.py @@ -165,6 +165,10 @@ EVENT_LOGBOOK_ENTRY = 'logbook_entry' EVENT_THEMES_UPDATED = 'themes_updated' +# #### INTENTS #### +INTENT_TURN_OFF = 'HassTurnOff' +INTENT_TURN_ON = 'HassTurnOn' + # #### STATES #### STATE_ON = 'on' STATE_OFF = 'off' diff --git a/tests/components/test_conversation.py b/tests/components/test_conversation.py index fab1e24d8e7d2b..fc35c780670f95 100644 --- a/tests/components/test_conversation.py +++ b/tests/components/test_conversation.py @@ -6,7 +6,9 @@ from homeassistant.setup import async_setup_component from homeassistant.components import conversation +import homeassistant.components as component from homeassistant.helpers import intent +from homeassistant.util.async import run_coroutine_threadsafe from tests.common import async_mock_intent, async_mock_service @@ -16,6 +18,9 @@ def test_calling_intent(hass): """Test calling an intent from a conversation.""" intents = async_mock_intent(hass, 'OrderBeer') + result = yield from component.async_setup(hass, {}) + assert result + result = yield from async_setup_component(hass, 'conversation', { 'conversation': { 'intents': { @@ -46,6 +51,9 @@ def test_register_before_setup(hass): """Test calling an intent from a conversation.""" intents = async_mock_intent(hass, 'OrderBeer') + result = yield from component.async_setup(hass, {}) + assert result + hass.components.conversation.async_register('OrderBeer', [ 'A {type} beer, please' ]) @@ -107,6 +115,9 @@ def async_handle(self, intent): intent.async_register(hass, TestIntentHandler()) + result = yield from component.async_setup(hass, {}) + assert result + result = yield from async_setup_component(hass, 'conversation', { 'conversation': { 'intents': { @@ -145,6 +156,9 @@ def async_handle(self, intent): @pytest.mark.parametrize('sentence', ('turn on kitchen', 'turn kitchen on')) def test_turn_on_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 @@ -168,6 +182,9 @@ def test_turn_on_intent(hass, sentence): @pytest.mark.parametrize('sentence', ('turn off kitchen', 'turn kitchen off')) def test_turn_off_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 @@ -190,6 +207,9 @@ def test_turn_off_intent(hass, sentence): @asyncio.coroutine def test_http_api(hass, test_client): """Test the HTTP conversation API.""" + result = yield from component.async_setup(hass, {}) + assert result + result = yield from async_setup_component(hass, 'conversation', {}) assert result @@ -212,6 +232,9 @@ def test_http_api(hass, test_client): @asyncio.coroutine def test_http_api_wrong_data(hass, test_client): """Test the HTTP conversation API.""" + result = yield from component.async_setup(hass, {}) + assert result + result = yield from async_setup_component(hass, 'conversation', {}) assert result From bf015c932ac8c845ddec43b3b7aab6284c6c6c3e Mon Sep 17 00:00:00 2001 From: tschmidty69 Date: Sat, 3 Feb 2018 10:57:40 -0500 Subject: [PATCH 02/26] Removed unused import --- tests/components/test_conversation.py | 1 - 1 file changed, 1 deletion(-) diff --git a/tests/components/test_conversation.py b/tests/components/test_conversation.py index fc35c780670f95..92bc8136286745 100644 --- a/tests/components/test_conversation.py +++ b/tests/components/test_conversation.py @@ -8,7 +8,6 @@ from homeassistant.components import conversation import homeassistant.components as component from homeassistant.helpers import intent -from homeassistant.util.async import run_coroutine_threadsafe from tests.common import async_mock_intent, async_mock_service From b3d7bf650da834bec96544beb612393a6dd91c2f Mon Sep 17 00:00:00 2001 From: tschmidty69 Date: Sat, 3 Feb 2018 13:14:42 -0500 Subject: [PATCH 03/26] Lint fix which my local runs dont catch apparently... --- homeassistant/components/__init__.py | 2 +- homeassistant/components/conversation.py | 3 --- 2 files changed, 1 insertion(+), 4 deletions(-) diff --git a/homeassistant/components/__init__.py b/homeassistant/components/__init__.py index 1723be11855e6a..c0bb5bf45fa09d 100644 --- a/homeassistant/components/__init__.py +++ b/homeassistant/components/__init__.py @@ -30,6 +30,7 @@ SERVICE_RELOAD_CORE_CONFIG = 'reload_core_config' SERVICE_CHECK_CONFIG = 'check_config' + def is_on(hass, entity_id=None): """Load up the module to call the is_on method. @@ -161,7 +162,6 @@ def async_handle_turn_service(service): hass.helpers.intent.async_register(TurnOnIntent()) hass.helpers.intent.async_register(TurnOffIntent()) - @asyncio.coroutine def async_handle_core_service(call): """Service handler for handling core services.""" diff --git a/homeassistant/components/conversation.py b/homeassistant/components/conversation.py index 463fec820952fb..405b77fd54ff07 100644 --- a/homeassistant/components/conversation.py +++ b/homeassistant/components/conversation.py @@ -7,7 +7,6 @@ import asyncio import logging import re -import warnings import voluptuous as vol @@ -71,7 +70,6 @@ def async_register(hass, intent_type, utterances): @asyncio.coroutine def async_setup(hass, config): """Register the process service.""" - config = config.get(DOMAIN, {}) intents = hass.data.get(DOMAIN) @@ -146,7 +144,6 @@ def _process(hass, text): class ConversationProcessView(http.HomeAssistantView): """View to retrieve shopping list content.""" - url = '/api/conversation/process' name = "api:conversation:process" From 86515665be6a8e9d47c4366f5236bf8497cc1041 Mon Sep 17 00:00:00 2001 From: tschmidty69 Date: Sun, 4 Feb 2018 17:11:36 -0500 Subject: [PATCH 04/26] Moved hass intent code into intent --- homeassistant/components/__init__.py | 80 +++++++----------------- homeassistant/components/conversation.py | 11 ++-- homeassistant/const.py | 1 + homeassistant/helpers/intent.py | 43 ++++++++++++- 4 files changed, 69 insertions(+), 66 deletions(-) diff --git a/homeassistant/components/__init__.py b/homeassistant/components/__init__.py index c0bb5bf45fa09d..3bf9ef416e3ae3 100644 --- a/homeassistant/components/__init__.py +++ b/homeassistant/components/__init__.py @@ -18,13 +18,11 @@ from homeassistant.helpers.service import extract_entity_ids from homeassistant.helpers import intent from homeassistant.const import ( - ATTR_ENTITY_ID, INTENT_TURN_ON, INTENT_TURN_OFF, + ATTR_ENTITY_ID, INTENT_TURN_ON, INTENT_TURN_OFF, INTENT_TOGGLE, SERVICE_TURN_ON, SERVICE_TURN_OFF, SERVICE_TOGGLE, SERVICE_HOMEASSISTANT_STOP, SERVICE_HOMEASSISTANT_RESTART, RESTART_EXIT_CODE) -REQUIREMENTS = ['fuzzywuzzy==0.16.0'] - _LOGGER = logging.getLogger(__name__) SERVICE_RELOAD_CORE_CONFIG = 'reload_core_config' @@ -161,6 +159,7 @@ def async_handle_turn_service(service): ha.DOMAIN, SERVICE_TOGGLE, async_handle_turn_service) hass.helpers.intent.async_register(TurnOnIntent()) hass.helpers.intent.async_register(TurnOffIntent()) + hass.helpers.intent.async_register(ToggleIntent()) @asyncio.coroutine def async_handle_core_service(call): @@ -209,74 +208,37 @@ def async_handle_reload_config(call): return True -@ha.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.""" + """Handle component turn 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( - ha.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 + domain = ha.DOMAIN + service = SERVICE_TURN_ON + response = "Turned on {}" class TurnOffIntent(intent.IntentHandler): - """Handle turning item off intents.""" + """Handle component turn intents.""" intent_type = INTENT_TURN_OFF slot_schema = { 'name': cv.string, } + domain = ha.DOMAIN + service = SERVICE_TURN_OFF + response = "Turned off {}" - @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( - ha.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 ToggleIntent(intent.IntentHandler): + """Handle component turn intents.""" + + intent_type = INTENT_TOGGLE + slot_schema = { + 'name': cv.string, + } + domain = ha.DOMAIN + service = SERVICE_TOGGLE + response = "Toggled {}" diff --git a/homeassistant/components/conversation.py b/homeassistant/components/conversation.py index 405b77fd54ff07..7a5bfd3836a0d7 100644 --- a/homeassistant/components/conversation.py +++ b/homeassistant/components/conversation.py @@ -12,13 +12,12 @@ from homeassistant import core from homeassistant.components import http -from homeassistant.const import INTENT_TURN_ON, INTENT_TURN_OFF +from homeassistant.const import (INTENT_TURN_ON, + INTENT_TURN_OFF, INTENT_TOGGLE) from homeassistant.helpers import config_validation as cv from homeassistant.helpers import intent from homeassistant.loader import bind_hass -REQUIREMENTS = ['fuzzywuzzy==0.16.0'] - _LOGGER = logging.getLogger(__name__) ATTR_TEXT = 'text' @@ -97,8 +96,10 @@ def process(service): async_register(hass, INTENT_TURN_ON, ['Turn {name} on', 'Turn on {name}']) - async_register(hass, INTENT_TURN_OFF, [ - 'Turn {name} off', 'Turn off {name}']) + async_register(hass, INTENT_TURN_OFF, + ['Turn {name} off', 'Turn off {name}']) + async_register(hass, INTENT_TOGGLE, + ['Toggle {name}', 'Toggle {name}']) return True diff --git a/homeassistant/const.py b/homeassistant/const.py index d335e2814a5414..aca0f1ac48cabd 100644 --- a/homeassistant/const.py +++ b/homeassistant/const.py @@ -168,6 +168,7 @@ # #### INTENTS #### INTENT_TURN_OFF = 'HassTurnOff' INTENT_TURN_ON = 'HassTurnOn' +INTENT_TOGGLE = 'HassToggle' # #### STATES #### STATE_ON = 'on' diff --git a/homeassistant/helpers/intent.py b/homeassistant/helpers/intent.py index 0cf9d83863f49f..6da9124086b683 100644 --- a/homeassistant/helpers/intent.py +++ b/homeassistant/helpers/intent.py @@ -7,14 +7,18 @@ from homeassistant.core import callback from homeassistant.exceptions import HomeAssistantError from homeassistant.loader import bind_hass +from homeassistant.const import ATTR_ENTITY_ID + +REQUIREMENTS = ['fuzzywuzzy==0.16.0', 'python-Levenshtein==0.12.0'] -DATA_KEY = 'intent' _LOGGER = logging.getLogger(__name__) SLOT_SCHEMA = vol.Schema({ }, extra=vol.ALLOW_EXTRA) +DATA_KEY = 'intent' + SPEECH_TYPE_PLAIN = 'plain' SPEECH_TYPE_SSML = 'ssml' @@ -80,6 +84,17 @@ class IntentHandleError(IntentError): pass +@callback +@bind_hass +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 IntentHandler: """Intent handler registration.""" @@ -88,12 +103,16 @@ class IntentHandler: slot_schema = None _slot_schema = None platforms = None + domain = None + service = None + response = '' @callback def async_can_handle(self, intent_obj): """Test if an intent can be handled.""" return self.platforms is None or intent_obj.platform in self.platforms + @callback def async_validate_slots(self, slots): """Validate slot information.""" @@ -110,7 +129,27 @@ def async_validate_slots(self, slots): @asyncio.coroutine def async_handle(self, intent_obj): """Handle the intent.""" - raise NotImplementedError() + if not self.service: + raise NotImplementedError() + 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( + self.domain, self.service, { + ATTR_ENTITY_ID: entity.entity_id, + }, blocking=True) + + response = intent_obj.create_response() + response.async_set_speech( + self.response.format(entity.name)) + return response + def __repr__(self): """Represent a string of an intent handler.""" From c842be27ea4cf62526ed898eb1741b6024f373e1 Mon Sep 17 00:00:00 2001 From: tschmidty69 Date: Sun, 4 Feb 2018 17:17:53 -0500 Subject: [PATCH 05/26] Added test for toggle to conversation. --- tests/components/test_conversation.py | 26 ++++++++++++++++++++++++++ 1 file changed, 26 insertions(+) diff --git a/tests/components/test_conversation.py b/tests/components/test_conversation.py index 92bc8136286745..2c121771adf291 100644 --- a/tests/components/test_conversation.py +++ b/tests/components/test_conversation.py @@ -203,6 +203,32 @@ def test_turn_off_intent(hass, sentence): assert call.data == {'entity_id': 'light.kitchen'} +@asyncio.coroutine +@pytest.mark.parametrize('sentence', ('toggle kitchen')) +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 def test_http_api(hass, test_client): """Test the HTTP conversation API.""" From 04b407ab79e6b67db0fcc7e2c44cb9c8f674bd86 Mon Sep 17 00:00:00 2001 From: tschmidty69 Date: Sun, 4 Feb 2018 17:48:30 -0500 Subject: [PATCH 06/26] Fixed toggle tests --- homeassistant/components/conversation.py | 2 +- tests/components/test_conversation.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/homeassistant/components/conversation.py b/homeassistant/components/conversation.py index 7a5bfd3836a0d7..3303e33b627374 100644 --- a/homeassistant/components/conversation.py +++ b/homeassistant/components/conversation.py @@ -99,7 +99,7 @@ def process(service): async_register(hass, INTENT_TURN_OFF, ['Turn {name} off', 'Turn off {name}']) async_register(hass, INTENT_TOGGLE, - ['Toggle {name}', 'Toggle {name}']) + ['Toggle {name}', '{name} toggle']) return True diff --git a/tests/components/test_conversation.py b/tests/components/test_conversation.py index 2c121771adf291..83b528849f2954 100644 --- a/tests/components/test_conversation.py +++ b/tests/components/test_conversation.py @@ -204,7 +204,7 @@ def test_turn_off_intent(hass, sentence): @asyncio.coroutine -@pytest.mark.parametrize('sentence', ('toggle kitchen')) +@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, {}) From c4790750b6336ea2912a268c25b94df47c8a0a7d Mon Sep 17 00:00:00 2001 From: Tod Schmidt Date: Sun, 4 Feb 2018 17:58:40 -0500 Subject: [PATCH 07/26] Update intent.py --- homeassistant/helpers/intent.py | 1 + 1 file changed, 1 insertion(+) diff --git a/homeassistant/helpers/intent.py b/homeassistant/helpers/intent.py index 6da9124086b683..c04a6a334055cc 100644 --- a/homeassistant/helpers/intent.py +++ b/homeassistant/helpers/intent.py @@ -84,6 +84,7 @@ class IntentHandleError(IntentError): pass + @callback @bind_hass def _match_entity(hass, name): From 13d0d9ad3951eb55fe48aa884a7f67f8564f1c64 Mon Sep 17 00:00:00 2001 From: tschmidty69 Date: Sun, 4 Feb 2018 19:04:30 -0500 Subject: [PATCH 08/26] Added homeassistant.helpers to gen_requirements script. --- homeassistant/helpers/intent.py | 1 - requirements_all.txt | 5 ++++- requirements_test_all.txt | 5 ++++- script/gen_requirements_all.py | 4 +++- 4 files changed, 11 insertions(+), 4 deletions(-) diff --git a/homeassistant/helpers/intent.py b/homeassistant/helpers/intent.py index 6da9124086b683..179090def7fd76 100644 --- a/homeassistant/helpers/intent.py +++ b/homeassistant/helpers/intent.py @@ -11,7 +11,6 @@ REQUIREMENTS = ['fuzzywuzzy==0.16.0', 'python-Levenshtein==0.12.0'] - _LOGGER = logging.getLogger(__name__) SLOT_SCHEMA = vol.Schema({ diff --git a/requirements_all.txt b/requirements_all.txt index 67fac8e89d8384..1e448152fda753 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -294,7 +294,7 @@ fritzhome==1.0.4 # homeassistant.components.media_player.frontier_silicon fsapi==0.0.7 -# homeassistant.components.conversation +# homeassistant.helpers.intent fuzzywuzzy==0.16.0 # homeassistant.components.tts.google @@ -866,6 +866,9 @@ pyteleloisirs==3.3 # homeassistant.components.switch.thinkingcleaner pythinkingcleaner==0.0.3 +# homeassistant.helpers.intent +python-Levenshtein==0.12.0 + # homeassistant.components.sensor.blockchain python-blockchain-api==0.0.2 diff --git a/requirements_test_all.txt b/requirements_test_all.txt index f21a20f7439056..8cebd340e2ba33 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -56,7 +56,7 @@ evohomeclient==0.2.5 # homeassistant.components.sensor.geo_rss_events feedparser==5.2.1 -# homeassistant.components.conversation +# homeassistant.helpers.intent fuzzywuzzy==0.16.0 # homeassistant.components.tts.google @@ -136,6 +136,9 @@ pymonoprice==0.3 # homeassistant.components.binary_sensor.nx584 pynx584==0.4 +# homeassistant.helpers.intent +python-Levenshtein==0.12.0 + # homeassistant.components.sensor.darksky # homeassistant.components.weather.darksky python-forecastio==1.3.5 diff --git a/script/gen_requirements_all.py b/script/gen_requirements_all.py index dcd201667ddc66..d041626740ca92 100755 --- a/script/gen_requirements_all.py +++ b/script/gen_requirements_all.py @@ -66,6 +66,7 @@ 'pushbullet.py', 'py-canary', 'pydispatcher', + 'python-Levenshtein', 'PyJWT', 'pylitejet', 'pymonoprice', @@ -150,7 +151,8 @@ def gather_modules(): errors = [] for package in sorted(explore_module('homeassistant.components', True) + - explore_module('homeassistant.scripts', True)): + explore_module('homeassistant.scripts', True) + + explore_module('homeassistant.helpers', True)): try: module = importlib.import_module(package) except ImportError: From 2dcfe1833c2d025fd4c8968334f0c3cda9986664 Mon Sep 17 00:00:00 2001 From: Tod Schmidt Date: Mon, 5 Feb 2018 16:55:13 -0500 Subject: [PATCH 09/26] Update intent.py --- homeassistant/helpers/intent.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/homeassistant/helpers/intent.py b/homeassistant/helpers/intent.py index e83993b68b8e21..b04219a9ff098a 100644 --- a/homeassistant/helpers/intent.py +++ b/homeassistant/helpers/intent.py @@ -93,7 +93,8 @@ def _match_entity(hass, name): 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 + state = hass.states.get(entity_id) if entity_id else None + return state.entity_id if state else None class IntentHandler: @@ -142,7 +143,7 @@ def async_handle(self, intent_obj): yield from hass.services.async_call( self.domain, self.service, { - ATTR_ENTITY_ID: entity.entity_id, + ATTR_ENTITY_ID: entity, }, blocking=True) response = intent_obj.create_response() From 861cdc59f0ec15137e079e5226c9acab202167e0 Mon Sep 17 00:00:00 2001 From: Tod Schmidt Date: Mon, 5 Feb 2018 17:21:11 -0500 Subject: [PATCH 10/26] Update intent.py --- homeassistant/helpers/intent.py | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/homeassistant/helpers/intent.py b/homeassistant/helpers/intent.py index b04219a9ff098a..57ddcf62a00d36 100644 --- a/homeassistant/helpers/intent.py +++ b/homeassistant/helpers/intent.py @@ -113,7 +113,6 @@ def async_can_handle(self, intent_obj): """Test if an intent can be handled.""" return self.platforms is None or intent_obj.platform in self.platforms - @callback def async_validate_slots(self, slots): """Validate slot information.""" @@ -143,7 +142,7 @@ def async_handle(self, intent_obj): yield from hass.services.async_call( self.domain, self.service, { - ATTR_ENTITY_ID: entity, + ATTR_ENTITY_ID: entity }, blocking=True) response = intent_obj.create_response() @@ -151,7 +150,6 @@ def async_handle(self, intent_obj): self.response.format(entity.name)) return response - def __repr__(self): """Represent a string of an intent handler.""" return '<{} - {}>'.format(self.__class__.__name__, self.intent_type) From 2214096f3d91e1aedc100f9840d5eca90c3698ff Mon Sep 17 00:00:00 2001 From: tschmidty69 Date: Mon, 5 Feb 2018 18:49:23 -0500 Subject: [PATCH 11/26] Changed return value for _match_entity --- homeassistant/components/conversation.py | 1 + homeassistant/helpers/intent.py | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/homeassistant/components/conversation.py b/homeassistant/components/conversation.py index 3303e33b627374..5e1eb23f5e063d 100644 --- a/homeassistant/components/conversation.py +++ b/homeassistant/components/conversation.py @@ -145,6 +145,7 @@ def _process(hass, text): class ConversationProcessView(http.HomeAssistantView): """View to retrieve shopping list content.""" + url = '/api/conversation/process' name = "api:conversation:process" diff --git a/homeassistant/helpers/intent.py b/homeassistant/helpers/intent.py index 57ddcf62a00d36..a5159d0b5ba404 100644 --- a/homeassistant/helpers/intent.py +++ b/homeassistant/helpers/intent.py @@ -147,7 +147,7 @@ def async_handle(self, intent_obj): response = intent_obj.create_response() response.async_set_speech( - self.response.format(entity.name)) + self.response.format(entity)) return response def __repr__(self): From 90280d7a00388af9ebf3043eb147e06a85228e66 Mon Sep 17 00:00:00 2001 From: tschmidty69 Date: Tue, 6 Feb 2018 10:54:19 -0500 Subject: [PATCH 12/26] Moved consts and requirements --- homeassistant/components/__init__.py | 5 +++-- homeassistant/components/conversation.py | 6 ++++-- homeassistant/const.py | 5 ----- homeassistant/helpers/intent.py | 10 +++++++--- requirements_all.txt | 4 ++-- requirements_test_all.txt | 4 ++-- script/gen_requirements_all.py | 3 +-- 7 files changed, 19 insertions(+), 18 deletions(-) diff --git a/homeassistant/components/__init__.py b/homeassistant/components/__init__.py index 3bf9ef416e3ae3..eda33302e160a4 100644 --- a/homeassistant/components/__init__.py +++ b/homeassistant/components/__init__.py @@ -17,9 +17,10 @@ from homeassistant.helpers import config_validation as cv from homeassistant.helpers.service import extract_entity_ids from homeassistant.helpers import intent +from homeassistant.helpers.intent import (INTENT_TURN_ON, + INTENT_TURN_OFF, INTENT_TOGGLE) from homeassistant.const import ( - ATTR_ENTITY_ID, INTENT_TURN_ON, INTENT_TURN_OFF, INTENT_TOGGLE, - 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, RESTART_EXIT_CODE) diff --git a/homeassistant/components/conversation.py b/homeassistant/components/conversation.py index 5e1eb23f5e063d..6cf20b0507f381 100644 --- a/homeassistant/components/conversation.py +++ b/homeassistant/components/conversation.py @@ -12,12 +12,14 @@ from homeassistant import core from homeassistant.components import http -from homeassistant.const import (INTENT_TURN_ON, - INTENT_TURN_OFF, INTENT_TOGGLE) +from homeassistant.helpers.intent import (INTENT_TURN_ON, + INTENT_TURN_OFF, INTENT_TOGGLE) from homeassistant.helpers import config_validation as cv from homeassistant.helpers import intent from homeassistant.loader import bind_hass +REQUIREMENTS = ['fuzzywuzzy==0.16.0', 'python-Levenshtein==0.12.0'] + _LOGGER = logging.getLogger(__name__) ATTR_TEXT = 'text' diff --git a/homeassistant/const.py b/homeassistant/const.py index aca0f1ac48cabd..560c99bb653bca 100644 --- a/homeassistant/const.py +++ b/homeassistant/const.py @@ -165,11 +165,6 @@ EVENT_LOGBOOK_ENTRY = 'logbook_entry' EVENT_THEMES_UPDATED = 'themes_updated' -# #### INTENTS #### -INTENT_TURN_OFF = 'HassTurnOff' -INTENT_TURN_ON = 'HassTurnOn' -INTENT_TOGGLE = 'HassToggle' - # #### STATES #### STATE_ON = 'on' STATE_OFF = 'off' diff --git a/homeassistant/helpers/intent.py b/homeassistant/helpers/intent.py index a5159d0b5ba404..e253f382622c8d 100644 --- a/homeassistant/helpers/intent.py +++ b/homeassistant/helpers/intent.py @@ -2,6 +2,7 @@ import asyncio import logging +import fuzzywuzzy import voluptuous as vol from homeassistant.core import callback @@ -9,10 +10,13 @@ from homeassistant.loader import bind_hass from homeassistant.const import ATTR_ENTITY_ID -REQUIREMENTS = ['fuzzywuzzy==0.16.0', 'python-Levenshtein==0.12.0'] - _LOGGER = logging.getLogger(__name__) +# #### INTENTS #### +INTENT_TURN_OFF = 'HassTurnOff' +INTENT_TURN_ON = 'HassTurnOn' +INTENT_TOGGLE = 'HassToggle' + SLOT_SCHEMA = vol.Schema({ }, extra=vol.ALLOW_EXTRA) @@ -103,7 +107,7 @@ class IntentHandler: intent_type = None slot_schema = None _slot_schema = None - platforms = None + platforms = [] domain = None service = None response = '' diff --git a/requirements_all.txt b/requirements_all.txt index 1e448152fda753..dc76148641de46 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -294,7 +294,7 @@ fritzhome==1.0.4 # homeassistant.components.media_player.frontier_silicon fsapi==0.0.7 -# homeassistant.helpers.intent +# homeassistant.components.conversation fuzzywuzzy==0.16.0 # homeassistant.components.tts.google @@ -866,7 +866,7 @@ pyteleloisirs==3.3 # homeassistant.components.switch.thinkingcleaner pythinkingcleaner==0.0.3 -# homeassistant.helpers.intent +# homeassistant.components.conversation python-Levenshtein==0.12.0 # homeassistant.components.sensor.blockchain diff --git a/requirements_test_all.txt b/requirements_test_all.txt index 8cebd340e2ba33..991ae14b93b85d 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -56,7 +56,7 @@ evohomeclient==0.2.5 # homeassistant.components.sensor.geo_rss_events feedparser==5.2.1 -# homeassistant.helpers.intent +# homeassistant.components.conversation fuzzywuzzy==0.16.0 # homeassistant.components.tts.google @@ -136,7 +136,7 @@ pymonoprice==0.3 # homeassistant.components.binary_sensor.nx584 pynx584==0.4 -# homeassistant.helpers.intent +# homeassistant.components.conversation python-Levenshtein==0.12.0 # homeassistant.components.sensor.darksky diff --git a/script/gen_requirements_all.py b/script/gen_requirements_all.py index d041626740ca92..d057ba4fe1bff7 100755 --- a/script/gen_requirements_all.py +++ b/script/gen_requirements_all.py @@ -151,8 +151,7 @@ def gather_modules(): errors = [] for package in sorted(explore_module('homeassistant.components', True) + - explore_module('homeassistant.scripts', True) + - explore_module('homeassistant.helpers', True)): + explore_module('homeassistant.scripts', True)): try: module = importlib.import_module(package) except ImportError: From f2876894be81d3bbdc7663e600efa38c4af03897 Mon Sep 17 00:00:00 2001 From: tschmidty69 Date: Tue, 6 Feb 2018 11:24:47 -0500 Subject: [PATCH 13/26] Removed unused import --- homeassistant/helpers/intent.py | 1 - 1 file changed, 1 deletion(-) diff --git a/homeassistant/helpers/intent.py b/homeassistant/helpers/intent.py index e253f382622c8d..237085541f2b2e 100644 --- a/homeassistant/helpers/intent.py +++ b/homeassistant/helpers/intent.py @@ -2,7 +2,6 @@ import asyncio import logging -import fuzzywuzzy import voluptuous as vol from homeassistant.core import callback From 7992a7361b0ad43300a6f9a03911a6357a3846a8 Mon Sep 17 00:00:00 2001 From: tschmidty69 Date: Tue, 6 Feb 2018 15:37:17 -0500 Subject: [PATCH 14/26] Removed http view --- homeassistant/components/__init__.py | 3 ++- homeassistant/helpers/intent.py | 2 ++ 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/homeassistant/components/__init__.py b/homeassistant/components/__init__.py index eda33302e160a4..1ff8ed77478887 100644 --- a/homeassistant/components/__init__.py +++ b/homeassistant/components/__init__.py @@ -13,6 +13,7 @@ import homeassistant.core as ha import homeassistant.config as conf_util +from homeassistant.components import http from homeassistant.exceptions import HomeAssistantError from homeassistant.helpers import config_validation as cv from homeassistant.helpers.service import extract_entity_ids @@ -29,7 +30,6 @@ SERVICE_RELOAD_CORE_CONFIG = 'reload_core_config' SERVICE_CHECK_CONFIG = 'check_config' - def is_on(hass, entity_id=None): """Load up the module to call the is_on method. @@ -162,6 +162,7 @@ def async_handle_turn_service(service): hass.helpers.intent.async_register(TurnOffIntent()) hass.helpers.intent.async_register(ToggleIntent()) + @asyncio.coroutine def async_handle_core_service(call): """Service handler for handling core services.""" diff --git a/homeassistant/helpers/intent.py b/homeassistant/helpers/intent.py index 237085541f2b2e..c666031fa25786 100644 --- a/homeassistant/helpers/intent.py +++ b/homeassistant/helpers/intent.py @@ -24,6 +24,8 @@ SPEECH_TYPE_PLAIN = 'plain' SPEECH_TYPE_SSML = 'ssml' +DEPENDENCIES = ['http'] + @callback @bind_hass From 5e987e85a1d4a2680c707418cf84984f4f646f19 Mon Sep 17 00:00:00 2001 From: tschmidty69 Date: Tue, 6 Feb 2018 16:26:23 -0500 Subject: [PATCH 15/26] Removed http import --- homeassistant/components/__init__.py | 1 - 1 file changed, 1 deletion(-) diff --git a/homeassistant/components/__init__.py b/homeassistant/components/__init__.py index 1ff8ed77478887..2f32a63cb829a4 100644 --- a/homeassistant/components/__init__.py +++ b/homeassistant/components/__init__.py @@ -13,7 +13,6 @@ import homeassistant.core as ha import homeassistant.config as conf_util -from homeassistant.components import http from homeassistant.exceptions import HomeAssistantError from homeassistant.helpers import config_validation as cv from homeassistant.helpers.service import extract_entity_ids From 5496efd56e5c34a2cf24fd1a7f7d583d56d70d19 Mon Sep 17 00:00:00 2001 From: tschmidty69 Date: Wed, 7 Feb 2018 16:39:52 -0500 Subject: [PATCH 16/26] Removed fuzzywuzzy dependency --- homeassistant/components/__init__.py | 6 +- homeassistant/components/conversation.py | 6 +- homeassistant/helpers/intent.py | 79 ++++++++++++++---------- requirements_all.txt | 6 -- requirements_test_all.txt | 6 -- 5 files changed, 53 insertions(+), 50 deletions(-) diff --git a/homeassistant/components/__init__.py b/homeassistant/components/__init__.py index 2f32a63cb829a4..0d0f5c1c75ce36 100644 --- a/homeassistant/components/__init__.py +++ b/homeassistant/components/__init__.py @@ -209,7 +209,7 @@ def async_handle_reload_config(call): return True -class TurnOnIntent(intent.IntentHandler): +class TurnOnIntent(intent.ServiceIntentHandler): """Handle component turn intents.""" intent_type = INTENT_TURN_ON @@ -221,7 +221,7 @@ class TurnOnIntent(intent.IntentHandler): response = "Turned on {}" -class TurnOffIntent(intent.IntentHandler): +class TurnOffIntent(intent.ServiceIntentHandler): """Handle component turn intents.""" intent_type = INTENT_TURN_OFF @@ -233,7 +233,7 @@ class TurnOffIntent(intent.IntentHandler): response = "Turned off {}" -class ToggleIntent(intent.IntentHandler): +class ToggleIntent(intent.ServiceIntentHandler): """Handle component turn intents.""" intent_type = INTENT_TOGGLE diff --git a/homeassistant/components/conversation.py b/homeassistant/components/conversation.py index 6cf20b0507f381..8df1fe342b20f5 100644 --- a/homeassistant/components/conversation.py +++ b/homeassistant/components/conversation.py @@ -12,14 +12,12 @@ from homeassistant import core from homeassistant.components import http -from homeassistant.helpers.intent import (INTENT_TURN_ON, - INTENT_TURN_OFF, INTENT_TOGGLE) from homeassistant.helpers import config_validation as cv from homeassistant.helpers import intent +from homeassistant.helpers.intent import (INTENT_TURN_ON, INTENT_TURN_OFF, + INTENT_TOGGLE) from homeassistant.loader import bind_hass -REQUIREMENTS = ['fuzzywuzzy==0.16.0', 'python-Levenshtein==0.12.0'] - _LOGGER = logging.getLogger(__name__) ATTR_TEXT = 'text' diff --git a/homeassistant/helpers/intent.py b/homeassistant/helpers/intent.py index c666031fa25786..87624ed2f07e2c 100644 --- a/homeassistant/helpers/intent.py +++ b/homeassistant/helpers/intent.py @@ -1,6 +1,7 @@ """Module to coordinate user intentions.""" import asyncio import logging +import re import voluptuous as vol @@ -24,8 +25,6 @@ SPEECH_TYPE_PLAIN = 'plain' SPEECH_TYPE_SSML = 'ssml' -DEPENDENCIES = ['http'] - @callback @bind_hass @@ -89,19 +88,6 @@ class IntentHandleError(IntentError): pass -@callback -@bind_hass -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] - state = hass.states.get(entity_id) if entity_id else None - return state.entity_id if state else None - - class IntentHandler: """Intent handler registration.""" @@ -109,9 +95,6 @@ class IntentHandler: slot_schema = None _slot_schema = None platforms = [] - domain = None - service = None - response = '' @callback def async_can_handle(self, intent_obj): @@ -134,31 +117,65 @@ def async_validate_slots(self, slots): @asyncio.coroutine def async_handle(self, intent_obj): """Handle the intent.""" - if not self.service: - raise NotImplementedError() + raise NotImplementedError() + + def __repr__(self): + """Represent a string of an intent handler.""" + return '<{} - {}>'.format(self.__class__.__name__, self.intent_type) + +def fuzzyfinder(name, entities): + """Semi fuzzy matching function.""" + matches = [] + pattern = '.*?'.join(name) + regex = re.compile(pattern) # Compiles a regex. + for entity in entities: + match = regex.search(entity) # Checks if the current item matches the regex. + if match: + matches.append((len(match.group()), match.start(), entity)) + return [x for _, _, x in sorted(matches)] + + +class ServiceIntentHandler(IntentHandler): + """Intent handler registration.""" + + domain = None + service = None + response = '' + + @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'] - entity = _match_entity(hass, name) + entities = {state.entity_id: state.name for state + in hass.states.async_all()} + entity_name = name.replace(' ', '_').lower() + entity_name = entity_name.replace('the_', '') + + matches = fuzzyfinder(entity_name, entities) + entity_id = matches[0] if matches else None + _LOGGER.error("%s matched entity: %s", name, entity_id) - if not entity: - _LOGGER.error("Could not find entity id for %s", name) - return None + 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 (%s)", name, + entity_name) + return response yield from hass.services.async_call( self.domain, self.service, { - ATTR_ENTITY_ID: entity + ATTR_ENTITY_ID: entity_id }, blocking=True) - response = intent_obj.create_response() response.async_set_speech( - self.response.format(entity)) + self.response.format(name)) return response - def __repr__(self): - """Represent a string of an intent handler.""" - return '<{} - {}>'.format(self.__class__.__name__, self.intent_type) - class Intent: """Hold the intent.""" diff --git a/requirements_all.txt b/requirements_all.txt index dc76148641de46..29af03feff11f1 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -294,9 +294,6 @@ fritzhome==1.0.4 # homeassistant.components.media_player.frontier_silicon fsapi==0.0.7 -# homeassistant.components.conversation -fuzzywuzzy==0.16.0 - # homeassistant.components.tts.google gTTS-token==1.1.1 @@ -866,9 +863,6 @@ pyteleloisirs==3.3 # homeassistant.components.switch.thinkingcleaner pythinkingcleaner==0.0.3 -# homeassistant.components.conversation -python-Levenshtein==0.12.0 - # homeassistant.components.sensor.blockchain python-blockchain-api==0.0.2 diff --git a/requirements_test_all.txt b/requirements_test_all.txt index 991ae14b93b85d..8463b71c85da95 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -56,9 +56,6 @@ evohomeclient==0.2.5 # homeassistant.components.sensor.geo_rss_events feedparser==5.2.1 -# homeassistant.components.conversation -fuzzywuzzy==0.16.0 - # homeassistant.components.tts.google gTTS-token==1.1.1 @@ -136,9 +133,6 @@ pymonoprice==0.3 # homeassistant.components.binary_sensor.nx584 pynx584==0.4 -# homeassistant.components.conversation -python-Levenshtein==0.12.0 - # homeassistant.components.sensor.darksky # homeassistant.components.weather.darksky python-forecastio==1.3.5 From cb8ab73ae11344077bc13ef55432ac382b2e93e8 Mon Sep 17 00:00:00 2001 From: tschmidty69 Date: Wed, 7 Feb 2018 16:41:15 -0500 Subject: [PATCH 17/26] woof --- homeassistant/helpers/intent.py | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/homeassistant/helpers/intent.py b/homeassistant/helpers/intent.py index 87624ed2f07e2c..61fd88f0f3efde 100644 --- a/homeassistant/helpers/intent.py +++ b/homeassistant/helpers/intent.py @@ -123,13 +123,14 @@ def __repr__(self): """Represent a string of an intent handler.""" return '<{} - {}>'.format(self.__class__.__name__, self.intent_type) + def fuzzyfinder(name, entities): """Semi fuzzy matching function.""" matches = [] pattern = '.*?'.join(name) - regex = re.compile(pattern) # Compiles a regex. + regex = re.compile(pattern) for entity in entities: - match = regex.search(entity) # Checks if the current item matches the regex. + match = regex.search(entity) if match: matches.append((len(match.group()), match.start(), entity)) return [x for _, _, x in sorted(matches)] @@ -157,7 +158,7 @@ def async_handle(self, intent_obj): matches = fuzzyfinder(entity_name, entities) entity_id = matches[0] if matches else None - _LOGGER.error("%s matched entity: %s", name, entity_id) + _LOGGER.debug("%s matched entity: %s", name, entity_id) response = intent_obj.create_response() if not entity_id: From 2d9becd4a078216a1b0f365518f4a852dcb9d659 Mon Sep 17 00:00:00 2001 From: tschmidty69 Date: Wed, 7 Feb 2018 20:47:18 -0500 Subject: [PATCH 18/26] A few cleanups --- homeassistant/components/conversation.py | 9 ++++----- homeassistant/helpers/intent.py | 4 ++-- script/gen_requirements_all.py | 2 -- tests/components/test_conversation.py | 3 --- 4 files changed, 6 insertions(+), 12 deletions(-) diff --git a/homeassistant/components/conversation.py b/homeassistant/components/conversation.py index 8df1fe342b20f5..c1dd89d31cd07c 100644 --- a/homeassistant/components/conversation.py +++ b/homeassistant/components/conversation.py @@ -14,8 +14,7 @@ from homeassistant.components import http from homeassistant.helpers import config_validation as cv from homeassistant.helpers import intent -from homeassistant.helpers.intent import (INTENT_TURN_ON, INTENT_TURN_OFF, - INTENT_TOGGLE) + from homeassistant.loader import bind_hass _LOGGER = logging.getLogger(__name__) @@ -94,11 +93,11 @@ def process(service): hass.http.register_view(ConversationProcessView) - async_register(hass, INTENT_TURN_ON, + async_register(hass, intent.INTENT_TURN_ON, ['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}']) - async_register(hass, INTENT_TOGGLE, + async_register(hass, intent.INTENT_TOGGLE, ['Toggle {name}', '{name} toggle']) return True diff --git a/homeassistant/helpers/intent.py b/homeassistant/helpers/intent.py index 61fd88f0f3efde..434e007747660b 100644 --- a/homeassistant/helpers/intent.py +++ b/homeassistant/helpers/intent.py @@ -124,7 +124,7 @@ def __repr__(self): return '<{} - {}>'.format(self.__class__.__name__, self.intent_type) -def fuzzyfinder(name, entities): +def fuzzymatch(name, entities): """Semi fuzzy matching function.""" matches = [] pattern = '.*?'.join(name) @@ -156,7 +156,7 @@ def async_handle(self, intent_obj): entity_name = name.replace(' ', '_').lower() entity_name = entity_name.replace('the_', '') - matches = fuzzyfinder(entity_name, entities) + matches = fuzzymatch(entity_name, entities) entity_id = matches[0] if matches else None _LOGGER.debug("%s matched entity: %s", name, entity_id) diff --git a/script/gen_requirements_all.py b/script/gen_requirements_all.py index d057ba4fe1bff7..9c510d8339edab 100755 --- a/script/gen_requirements_all.py +++ b/script/gen_requirements_all.py @@ -46,7 +46,6 @@ 'ephem', 'evohomeclient', 'feedparser', - 'fuzzywuzzy', 'gTTS-token', 'ha-ffmpeg', 'haversine', @@ -66,7 +65,6 @@ 'pushbullet.py', 'py-canary', 'pydispatcher', - 'python-Levenshtein', 'PyJWT', 'pylitejet', 'pymonoprice', diff --git a/tests/components/test_conversation.py b/tests/components/test_conversation.py index 83b528849f2954..60395fbe28e9b4 100644 --- a/tests/components/test_conversation.py +++ b/tests/components/test_conversation.py @@ -114,9 +114,6 @@ def async_handle(self, intent): intent.async_register(hass, TestIntentHandler()) - result = yield from component.async_setup(hass, {}) - assert result - result = yield from async_setup_component(hass, 'conversation', { 'conversation': { 'intents': { From 0d955d327da375e35e55fef70736b8b8fb4aafe6 Mon Sep 17 00:00:00 2001 From: tschmidty69 Date: Thu, 8 Feb 2018 10:11:08 -0500 Subject: [PATCH 19/26] Added domain filtering to entities --- homeassistant/components/__init__.py | 50 ++++------------------------ homeassistant/helpers/intent.py | 22 ++++++++---- 2 files changed, 22 insertions(+), 50 deletions(-) diff --git a/homeassistant/components/__init__.py b/homeassistant/components/__init__.py index 0d0f5c1c75ce36..0a7651c3fce608 100644 --- a/homeassistant/components/__init__.py +++ b/homeassistant/components/__init__.py @@ -14,11 +14,8 @@ import homeassistant.core as ha import homeassistant.config as conf_util from homeassistant.exceptions import HomeAssistantError -from homeassistant.helpers import config_validation as cv from homeassistant.helpers.service import extract_entity_ids from homeassistant.helpers import intent -from homeassistant.helpers.intent import (INTENT_TURN_ON, - INTENT_TURN_OFF, INTENT_TOGGLE) from homeassistant.const import ( ATTR_ENTITY_ID, SERVICE_TURN_ON, SERVICE_TURN_OFF, SERVICE_TOGGLE, SERVICE_HOMEASSISTANT_STOP, SERVICE_HOMEASSISTANT_RESTART, @@ -29,6 +26,7 @@ SERVICE_RELOAD_CORE_CONFIG = 'reload_core_config' SERVICE_CHECK_CONFIG = 'check_config' + def is_on(hass, entity_id=None): """Load up the module to call the is_on method. @@ -157,10 +155,12 @@ def async_handle_turn_service(service): ha.DOMAIN, SERVICE_TURN_ON, async_handle_turn_service) hass.services.async_register( ha.DOMAIN, SERVICE_TOGGLE, async_handle_turn_service) - hass.helpers.intent.async_register(TurnOnIntent()) - hass.helpers.intent.async_register(TurnOffIntent()) - hass.helpers.intent.async_register(ToggleIntent()) - + 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 def async_handle_core_service(call): @@ -207,39 +207,3 @@ def async_handle_reload_config(call): ha.DOMAIN, SERVICE_RELOAD_CORE_CONFIG, async_handle_reload_config) return True - - -class TurnOnIntent(intent.ServiceIntentHandler): - """Handle component turn intents.""" - - intent_type = INTENT_TURN_ON - slot_schema = { - 'name': cv.string, - } - domain = ha.DOMAIN - service = SERVICE_TURN_ON - response = "Turned on {}" - - -class TurnOffIntent(intent.ServiceIntentHandler): - """Handle component turn intents.""" - - intent_type = INTENT_TURN_OFF - slot_schema = { - 'name': cv.string, - } - domain = ha.DOMAIN - service = SERVICE_TURN_OFF - response = "Turned off {}" - - -class ToggleIntent(intent.ServiceIntentHandler): - """Handle component turn intents.""" - - intent_type = INTENT_TOGGLE - slot_schema = { - 'name': cv.string, - } - domain = ha.DOMAIN - service = SERVICE_TOGGLE - response = "Toggled {}" diff --git a/homeassistant/helpers/intent.py b/homeassistant/helpers/intent.py index 434e007747660b..468e091833704d 100644 --- a/homeassistant/helpers/intent.py +++ b/homeassistant/helpers/intent.py @@ -7,12 +7,12 @@ from homeassistant.core import callback from homeassistant.exceptions import HomeAssistantError +from homeassistant.helpers import config_validation as cv from homeassistant.loader import bind_hass from homeassistant.const import ATTR_ENTITY_ID _LOGGER = logging.getLogger(__name__) -# #### INTENTS #### INTENT_TURN_OFF = 'HassTurnOff' INTENT_TURN_ON = 'HassTurnOn' INTENT_TOGGLE = 'HassToggle' @@ -125,7 +125,7 @@ def __repr__(self): def fuzzymatch(name, entities): - """Semi fuzzy matching function.""" + """Fuzzy matching function.""" matches = [] pattern = '.*?'.join(name) regex = re.compile(pattern) @@ -137,11 +137,17 @@ def fuzzymatch(name, entities): class ServiceIntentHandler(IntentHandler): - """Intent handler registration.""" + """Service Intent handler registration.""" + + slot_schema = { + 'name': cv.string, + } - domain = None - service = None - response = '' + def __init__(self, intent_type, domain, service, response): + self.intent_type = intent_type + self.domain = domain + self.service = service + self.response = response @asyncio.coroutine def async_handle(self, intent_obj): @@ -157,7 +163,9 @@ def async_handle(self, intent_obj): entity_name = entity_name.replace('the_', '') matches = fuzzymatch(entity_name, entities) - entity_id = matches[0] if matches else None + entity_id = next((x for x in matches if self.domain in x), None) + if not entity_id: + entity_id = matches[0] if matches else None _LOGGER.debug("%s matched entity: %s", name, entity_id) response = intent_obj.create_response() From 1262ba34cf20eeff38429bf7cb6c7bac9e1f9223 Mon Sep 17 00:00:00 2001 From: tschmidty69 Date: Thu, 8 Feb 2018 10:17:04 -0500 Subject: [PATCH 20/26] Clarified class doc string --- homeassistant/helpers/intent.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/homeassistant/helpers/intent.py b/homeassistant/helpers/intent.py index 468e091833704d..90505af5be1468 100644 --- a/homeassistant/helpers/intent.py +++ b/homeassistant/helpers/intent.py @@ -137,7 +137,10 @@ def fuzzymatch(name, entities): class ServiceIntentHandler(IntentHandler): - """Service Intent handler registration.""" + """Service Intent handler registration. + + Service specific intent handler that calls a service by name/entity_id. + """ slot_schema = { 'name': cv.string, From ed1bcf2aaca9f5b2bb7f44520452bc1eb9cb802b Mon Sep 17 00:00:00 2001 From: tschmidty69 Date: Thu, 8 Feb 2018 11:01:24 -0500 Subject: [PATCH 21/26] Added doc string --- homeassistant/helpers/intent.py | 1 + 1 file changed, 1 insertion(+) diff --git a/homeassistant/helpers/intent.py b/homeassistant/helpers/intent.py index 90505af5be1468..001c599e4ea619 100644 --- a/homeassistant/helpers/intent.py +++ b/homeassistant/helpers/intent.py @@ -147,6 +147,7 @@ class ServiceIntentHandler(IntentHandler): } def __init__(self, intent_type, domain, service, response): + """Create Service Intent Handler.""" self.intent_type = intent_type self.domain = domain self.service = service From 6cfce83e42b852ec687182099deeead7640ea57d Mon Sep 17 00:00:00 2001 From: tschmidty69 Date: Fri, 9 Feb 2018 09:29:16 -0500 Subject: [PATCH 22/26] Added test in test_init --- homeassistant/helpers/intent.py | 6 +-- tests/components/test_init.py | 91 +++++++++++++++++++++++++++++++++ 2 files changed, 94 insertions(+), 3 deletions(-) diff --git a/homeassistant/helpers/intent.py b/homeassistant/helpers/intent.py index 001c599e4ea619..35dccf8797c7e2 100644 --- a/homeassistant/helpers/intent.py +++ b/homeassistant/helpers/intent.py @@ -146,12 +146,12 @@ class ServiceIntentHandler(IntentHandler): 'name': cv.string, } - def __init__(self, intent_type, domain, service, response): + def __init__(self, intent_type, domain, service, speech): """Create Service Intent Handler.""" self.intent_type = intent_type self.domain = domain self.service = service - self.response = response + self.speech = speech @asyncio.coroutine def async_handle(self, intent_obj): @@ -186,7 +186,7 @@ def async_handle(self, intent_obj): }, blocking=True) response.async_set_speech( - self.response.format(name)) + self.speech.format(name)) return response diff --git a/tests/components/test_init.py b/tests/components/test_init.py index 06ba8a57508fcd..d74528e11d1c97 100644 --- a/tests/components/test_init.py +++ b/tests/components/test_init.py @@ -2,6 +2,7 @@ # pylint: disable=protected-access import asyncio import unittest +import inspect from unittest.mock import patch, Mock import yaml @@ -11,6 +12,8 @@ from homeassistant.const import ( STATE_ON, STATE_OFF, SERVICE_TURN_ON, SERVICE_TURN_OFF, SERVICE_TOGGLE) import homeassistant.components as comps +import homeassistant.components.group as group +import homeassistant.helpers.intent as intent from homeassistant.exceptions import HomeAssistantError from homeassistant.helpers import entity from homeassistant.util.async import run_coroutine_threadsafe @@ -195,3 +198,91 @@ def test_check_config(self, mock_check, mock_stop): self.hass.block_till_done() assert mock_check.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']} From 95d30c6a0f4f8a7e5a382af426b34c5dc351aef9 Mon Sep 17 00:00:00 2001 From: tschmidty69 Date: Fri, 9 Feb 2018 10:03:22 -0500 Subject: [PATCH 23/26] woof --- tests/components/test_init.py | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/tests/components/test_init.py b/tests/components/test_init.py index d74528e11d1c97..aa5b6ed274c1db 100644 --- a/tests/components/test_init.py +++ b/tests/components/test_init.py @@ -2,7 +2,6 @@ # pylint: disable=protected-access import asyncio import unittest -import inspect from unittest.mock import patch, Mock import yaml @@ -12,7 +11,6 @@ from homeassistant.const import ( STATE_ON, STATE_OFF, SERVICE_TURN_ON, SERVICE_TURN_OFF, SERVICE_TOGGLE) import homeassistant.components as comps -import homeassistant.components.group as group import homeassistant.helpers.intent as intent from homeassistant.exceptions import HomeAssistantError from homeassistant.helpers import entity @@ -199,6 +197,7 @@ def test_check_config(self, mock_check, mock_stop): assert mock_check.called assert not mock_stop.called + @asyncio.coroutine def test_turn_on_intent(hass): """Test HassTurnOn intent.""" @@ -220,6 +219,7 @@ def test_turn_on_intent(hass): assert call.service == 'turn_on' assert call.data == {'entity_id': ['light.test_light']} + @asyncio.coroutine def test_turn_off_intent(hass): """Test HassTurnOff intent.""" @@ -241,6 +241,7 @@ def test_turn_off_intent(hass): assert call.service == 'turn_off' assert call.data == {'entity_id': ['light.test_light']} + @asyncio.coroutine def test_toggle_intent(hass): """Test HassToggle intent.""" @@ -262,11 +263,13 @@ def test_toggle_intent(hass): 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.""" + This tests that matching finds the proper entity among similar names. + """ result = yield from comps.async_setup(hass, {}) assert result From 42273fca9a25f0de8372533866e99e4a00d561d0 Mon Sep 17 00:00:00 2001 From: tschmidty69 Date: Sat, 10 Feb 2018 17:42:55 -0500 Subject: [PATCH 24/26] Cleanup entity matching --- homeassistant/helpers/intent.py | 23 +++++++++-------------- 1 file changed, 9 insertions(+), 14 deletions(-) diff --git a/homeassistant/helpers/intent.py b/homeassistant/helpers/intent.py index 35dccf8797c7e2..f2b408d622495e 100644 --- a/homeassistant/helpers/intent.py +++ b/homeassistant/helpers/intent.py @@ -128,11 +128,11 @@ def fuzzymatch(name, entities): """Fuzzy matching function.""" matches = [] pattern = '.*?'.join(name) - regex = re.compile(pattern) - for entity in entities: - match = regex.search(entity) + 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)) + matches.append((len(match.group()), match.start(), entity_id)) return [x for _, _, x in sorted(matches)] @@ -163,27 +163,22 @@ def async_handle(self, intent_obj): name = slots['name']['value'] entities = {state.entity_id: state.name for state in hass.states.async_all()} - entity_name = name.replace(' ', '_').lower() - entity_name = entity_name.replace('the_', '') - matches = fuzzymatch(entity_name, entities) - entity_id = next((x for x in matches if self.domain in x), None) - if not entity_id: - entity_id = matches[0] if matches else None + 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 (%s)", name, - entity_name) + "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 - }, blocking=True) + }) response.async_set_speech( self.speech.format(name)) From a43bc92d30d3876690794cb9fb29d31c5f1e2f8e Mon Sep 17 00:00:00 2001 From: Tod Schmidt Date: Sat, 10 Feb 2018 18:44:09 -0500 Subject: [PATCH 25/26] Update intent.py --- homeassistant/helpers/intent.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/homeassistant/helpers/intent.py b/homeassistant/helpers/intent.py index f2b408d622495e..bf2773d32b85da 100644 --- a/homeassistant/helpers/intent.py +++ b/homeassistant/helpers/intent.py @@ -172,7 +172,7 @@ def async_handle(self, intent_obj): 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) + _LOGGER.error("Could not find entity id matching %s", name) return response yield from hass.services.async_call( From b2ad2f90f191fb548f37af54e6e485dbfaeb15a4 Mon Sep 17 00:00:00 2001 From: tschmidty69 Date: Sun, 11 Feb 2018 10:37:09 -0500 Subject: [PATCH 26/26] removed uneeded setup from tests --- tests/components/test_conversation.py | 3 --- 1 file changed, 3 deletions(-) diff --git a/tests/components/test_conversation.py b/tests/components/test_conversation.py index 60395fbe28e9b4..8d6293218534b6 100644 --- a/tests/components/test_conversation.py +++ b/tests/components/test_conversation.py @@ -50,9 +50,6 @@ def test_register_before_setup(hass): """Test calling an intent from a conversation.""" intents = async_mock_intent(hass, 'OrderBeer') - result = yield from component.async_setup(hass, {}) - assert result - hass.components.conversation.async_register('OrderBeer', [ 'A {type} beer, please' ])