-
-
Notifications
You must be signed in to change notification settings - Fork 31.8k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Move HassIntent handler code into helpers/intent #12181
Changes from 6 commits
eedb3d0
bf015c9
b3d7bf6
8651566
c842be2
04b407a
c479075
13d0d9a
ac5d2e5
2dcfe18
861cdc5
2214096
90280d7
f287689
7992a73
5e987e8
5496efd
cb8ab73
2d9becd
0d955d3
1262ba3
ed1bcf2
6cfce83
95d30c6
42273fc
a43bc92
b2ad2f9
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -165,6 +165,11 @@ | |
EVENT_LOGBOOK_ENTRY = 'logbook_entry' | ||
EVENT_THEMES_UPDATED = 'themes_updated' | ||
|
||
# #### INTENTS #### | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I think it's fine to keep these defined inside helpers.intent . There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. That works, components can just import them from intents, just felt a little cleaner to put the in consts but all good. |
||
INTENT_TURN_OFF = 'HassTurnOff' | ||
INTENT_TURN_ON = 'HassTurnOn' | ||
INTENT_TOGGLE = 'HassToggle' | ||
|
||
# #### STATES #### | ||
STATE_ON = 'on' | ||
STATE_OFF = 'off' | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -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'] | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Helpers can't have requirements. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Yeah, i saw that, but the problem in this particular case is that component/init.py isn't considered a module so doesn't allow requirements. I could put it anywhere under components but that raises a dependency issue. |
||
|
||
|
||
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 | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. expected 2 blank lines, found 1 |
||
@bind_hass | ||
def _match_entity(hass, name): | ||
"""Match a name to an entity.""" | ||
from fuzzywuzzy import process as fuzzyExtract | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This is installed as part of the conversation component. You can't use it here. The solution is that now we're going to need to add fuzzywuzzy as a core dependency. I just don't know if I like that. Is the added value of fuzzywuzzy that much that we need it? Could we just map against name? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Well, I put it in conversation so it would get added somewhere since I can't add a requirement to a helper. We could. Would be a little more convoluted with some regexs but I would guess we can cover most cases fairly easily. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. If the user doesn't have the conversation component enabled, it will not install the dependency. |
||
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: | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I don't like this, we're conflating concerns. Please extract this into a new class that extends from class ServiceIntentHandler(IntentHandler): There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Alternative, instead of a class, you can make it a method. @bind_hass
@callback
def expose_entity_service_intent(intent_name, domain, service): The automatic wrapping of a service in an intent has a very narrow use case. It only applies to services that only take in an entity_id. If it takes in anything more (like color in light.turn_on), you want to write your own. |
||
raise NotImplementedError() | ||
hass = intent_obj.hass | ||
slots = self.async_validate_slots(intent_obj.slots) | ||
name = slots['name']['value'] | ||
entity = _match_entity(hass, name) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This is retrieving a state not an entity. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. It;s a roundabout way of retrieving all entites, by getting all states and using the entities that belong to them. Need to add filtering by domain. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The state machine doesn't store entities, it stores states. You should change the variable name to reflect what it is. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The function returns the entity_id though and not the state. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. No, it returns a state: There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Ah, I see what you mean. I didn't write that bit. It would be more confusing to return a state, so rewrote the return There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Let's rename it to entity_id, as that is what is returned from |
||
|
||
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) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Let's remove blocking, it has been causing trouble for Alexa/Google Assistant as turning on can sometimes take a long time and we don't actually know if a service is successful. |
||
|
||
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.""" | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We should add this back where we import fuzzywuzzy. I remember it spamming a lot.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I see you've removed fuzzywuzzy, ok too 👍