Configure conversation for custom actions with keywords (#7734)
* - Simple keyword to action config * - Added more fuzzy stuff * - Logging & a bit of commenting * - pep8? * - pep8 and quick formatting fixes * - Changed configuration a bit * - Backwards compatibility tests * - Fallback or * - Added custom configuration for conversation * - Moved imports inside function * - pep8 * - Pass tests better * - Removed unused imports * - Moved warning ignore to above import for fuzzy * - Moved return for consistent return types * - Fallback if no choices to listen for * - Fixed linting errors * - Better logging and fixed linting errors(?) * - Fixed continuation * - Added one blank line after class docstring * Create conversation.py * Create test_conversation.py * Create test_conversation.py * Update test_conversation.pypull/8020/head
parent
843f8ce9ee
commit
7fae8cd0f1
|
@ -14,11 +14,13 @@ from homeassistant import core
|
||||||
from homeassistant.const import (
|
from homeassistant.const import (
|
||||||
ATTR_ENTITY_ID, SERVICE_TURN_OFF, SERVICE_TURN_ON)
|
ATTR_ENTITY_ID, SERVICE_TURN_OFF, SERVICE_TURN_ON)
|
||||||
import homeassistant.helpers.config_validation as cv
|
import homeassistant.helpers.config_validation as cv
|
||||||
|
from homeassistant.helpers import script
|
||||||
|
|
||||||
|
|
||||||
REQUIREMENTS = ['fuzzywuzzy==0.15.0']
|
REQUIREMENTS = ['fuzzywuzzy==0.15.0']
|
||||||
|
|
||||||
ATTR_TEXT = 'text'
|
ATTR_TEXT = 'text'
|
||||||
|
ATTR_SENTENCE = 'sentence'
|
||||||
DOMAIN = 'conversation'
|
DOMAIN = 'conversation'
|
||||||
|
|
||||||
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+)')
|
||||||
|
@ -29,9 +31,12 @@ SERVICE_PROCESS_SCHEMA = vol.Schema({
|
||||||
vol.Required(ATTR_TEXT): vol.All(cv.string, vol.Lower),
|
vol.Required(ATTR_TEXT): vol.All(cv.string, vol.Lower),
|
||||||
})
|
})
|
||||||
|
|
||||||
CONFIG_SCHEMA = vol.Schema({
|
CONFIG_SCHEMA = vol.Schema({DOMAIN: vol.Schema({
|
||||||
DOMAIN: vol.Schema({}),
|
cv.string: vol.Schema({
|
||||||
}, extra=vol.ALLOW_EXTRA)
|
vol.Required(ATTR_SENTENCE): cv.string,
|
||||||
|
vol.Required('action'): cv.SCRIPT_SCHEMA,
|
||||||
|
})
|
||||||
|
})}, extra=vol.ALLOW_EXTRA)
|
||||||
|
|
||||||
|
|
||||||
def setup(hass, config):
|
def setup(hass, config):
|
||||||
|
@ -40,9 +45,30 @@ def setup(hass, config):
|
||||||
from fuzzywuzzy import process as fuzzyExtract
|
from fuzzywuzzy import process as fuzzyExtract
|
||||||
|
|
||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
|
config = config.get(DOMAIN, {})
|
||||||
|
|
||||||
|
choices = {attrs[ATTR_SENTENCE]: script.Script(
|
||||||
|
hass,
|
||||||
|
attrs['action'],
|
||||||
|
name)
|
||||||
|
for name, attrs in config.items()}
|
||||||
|
|
||||||
def process(service):
|
def process(service):
|
||||||
"""Parse text into commands."""
|
"""Parse text into commands."""
|
||||||
|
# if actually configured
|
||||||
|
if choices:
|
||||||
|
text = service.data[ATTR_TEXT]
|
||||||
|
match = fuzzyExtract.extractOne(text, choices.keys())
|
||||||
|
scorelimit = 60 # arbitrary value
|
||||||
|
logging.info(
|
||||||
|
'matched up text %s and found %s',
|
||||||
|
text,
|
||||||
|
[match[0] if match[1] > scorelimit else 'nothing']
|
||||||
|
)
|
||||||
|
if match[1] > scorelimit:
|
||||||
|
choices[match[0]].run() # run respective script
|
||||||
|
return
|
||||||
|
|
||||||
text = service.data[ATTR_TEXT]
|
text = service.data[ATTR_TEXT]
|
||||||
match = REGEX_TURN_COMMAND.match(text)
|
match = REGEX_TURN_COMMAND.match(text)
|
||||||
|
|
||||||
|
|
|
@ -117,3 +117,46 @@ class TestConversation(unittest.TestCase):
|
||||||
conversation.DOMAIN, 'process', event_data, True))
|
conversation.DOMAIN, 'process', event_data, True))
|
||||||
self.assertTrue(mock_logger.called)
|
self.assertTrue(mock_logger.called)
|
||||||
self.assertFalse(mock_call.called)
|
self.assertFalse(mock_call.called)
|
||||||
|
|
||||||
|
|
||||||
|
class TestConfiguration(unittest.TestCase):
|
||||||
|
"""Test the conversation configuration component."""
|
||||||
|
|
||||||
|
# pylint: disable=invalid-name
|
||||||
|
def setUp(self):
|
||||||
|
"""Setup things to be run when tests are started."""
|
||||||
|
self.hass = get_test_home_assistant()
|
||||||
|
self.assertTrue(setup_component(self.hass, conversation.DOMAIN, {
|
||||||
|
conversation.DOMAIN: {
|
||||||
|
'test_2': {
|
||||||
|
'sentence': 'switch boolean',
|
||||||
|
'action': {
|
||||||
|
'service': 'input_boolean.toggle'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}))
|
||||||
|
|
||||||
|
# pylint: disable=invalid-name
|
||||||
|
def tearDown(self):
|
||||||
|
"""Stop everything that was started."""
|
||||||
|
self.hass.stop()
|
||||||
|
|
||||||
|
def test_custom(self):
|
||||||
|
"""Setup and perform good turn on requests."""
|
||||||
|
calls = []
|
||||||
|
|
||||||
|
@callback
|
||||||
|
def record_call(service):
|
||||||
|
"""Recorder for a call."""
|
||||||
|
calls.append(service)
|
||||||
|
|
||||||
|
self.hass.services.register('input_boolean', 'toggle', record_call)
|
||||||
|
|
||||||
|
event_data = {conversation.ATTR_TEXT: 'switch boolean'}
|
||||||
|
self.assertTrue(self.hass.services.call(
|
||||||
|
conversation.DOMAIN, 'process', event_data, True))
|
||||||
|
|
||||||
|
call = calls[-1]
|
||||||
|
self.assertEqual('input_boolean', call.domain)
|
||||||
|
self.assertEqual('toggle', call.service)
|
||||||
|
|
Loading…
Reference in New Issue