Snips add say and say_actions services (new) (#11596)

* Added snips.say and snips.say_action services

* Added snips.say and snips.say_action services

* Merged services.yaml changes I missed

* added tests for new service configs

* Woof

* Woof Woof

* Changed attribute names to follow hass standards.

* updated test_snips with new attribute names
This commit is contained in:
tschmidty69 2018-01-12 13:19:43 -05:00 committed by Paulus Schoutsen
parent cc236529c4
commit b8e4c2ff69
3 changed files with 158 additions and 1 deletions

View file

@ -364,6 +364,38 @@ abode:
description: Entity id of the quick action to trigger. description: Entity id of the quick action to trigger.
example: 'binary_sensor.home_quick_action' example: 'binary_sensor.home_quick_action'
snips:
say:
description: Send a TTS message to Snips.
fields:
text:
description: Text to say.
example: My name is snips
site_id:
description: Site to use to start session, defaults to default (optional)
example: bedroom
custom_data:
description: custom data that will be included with all messages in this session
example: user=UserName
say_action:
description: Send a TTS message to Snips to listen for a response.
fields:
text:
description: Text to say
example: My name is snips
site_id:
description: Site to use to start session, defaults to default (optional)
example: bedroom
custom_data:
description: custom data that will be included with all messages in this session
example: user=UserName
can_be_enqueued:
description: If True, session waits for an open session to end, if False session is dropped if one is running
example: True
intent_filter:
description: Optional Array of Strings - A list of intents names to restrict the NLU resolution to on the first query.
example: turnOnLights, turnOffLights
input_boolean: input_boolean:
toggle: toggle:
description: Toggles an input boolean. description: Toggles an input boolean.

View file

@ -8,17 +8,29 @@ import asyncio
import json import json
import logging import logging
from datetime import timedelta from datetime import timedelta
import voluptuous as vol import voluptuous as vol
from homeassistant.helpers import intent, config_validation as cv from homeassistant.helpers import intent, config_validation as cv
import homeassistant.components.mqtt as mqtt import homeassistant.components.mqtt as mqtt
DOMAIN = 'snips' DOMAIN = 'snips'
DEPENDENCIES = ['mqtt'] DEPENDENCIES = ['mqtt']
CONF_INTENTS = 'intents' CONF_INTENTS = 'intents'
CONF_ACTION = 'action' CONF_ACTION = 'action'
SERVICE_SAY = 'say'
SERVICE_SAY_ACTION = 'say_action'
INTENT_TOPIC = 'hermes/intent/#' INTENT_TOPIC = 'hermes/intent/#'
ATTR_TEXT = 'text'
ATTR_SITE_ID = 'site_id'
ATTR_CUSTOM_DATA = 'custom_data'
ATTR_CAN_BE_ENQUEUED = 'can_be_enqueued'
ATTR_INTENT_FILTER = 'intent_filter'
_LOGGER = logging.getLogger(__name__) _LOGGER = logging.getLogger(__name__)
CONFIG_SCHEMA = vol.Schema({ CONFIG_SCHEMA = vol.Schema({
@ -40,6 +52,20 @@ INTENT_SCHEMA = vol.Schema({
}] }]
}, extra=vol.ALLOW_EXTRA) }, extra=vol.ALLOW_EXTRA)
SERVICE_SCHEMA_SAY = vol.Schema({
vol.Required(ATTR_TEXT): str,
vol.Optional(ATTR_SITE_ID, default='default'): str,
vol.Optional(ATTR_CUSTOM_DATA, default=''): str
})
SERVICE_SCHEMA_SAY_ACTION = vol.Schema({
vol.Required(ATTR_TEXT): str,
vol.Optional(ATTR_SITE_ID, default='default'): str,
vol.Optional(ATTR_CUSTOM_DATA, default=''): str,
vol.Optional(ATTR_CAN_BE_ENQUEUED, default=True): cv.boolean,
vol.Optional(ATTR_INTENT_FILTER): vol.All(cv.ensure_list),
})
@asyncio.coroutine @asyncio.coroutine
def async_setup(hass, config): def async_setup(hass, config):
@ -93,6 +119,39 @@ def async_setup(hass, config):
yield from hass.components.mqtt.async_subscribe( yield from hass.components.mqtt.async_subscribe(
INTENT_TOPIC, message_received) INTENT_TOPIC, message_received)
@asyncio.coroutine
def snips_say(call):
"""Send a Snips notification message."""
notification = {'siteId': call.data.get(ATTR_SITE_ID, 'default'),
'customData': call.data.get(ATTR_CUSTOM_DATA, ''),
'init': {'type': 'notification',
'text': call.data.get(ATTR_TEXT)}}
mqtt.async_publish(hass, 'hermes/dialogueManager/startSession',
json.dumps(notification))
return
@asyncio.coroutine
def snips_say_action(call):
"""Send a Snips action message."""
notification = {'siteId': call.data.get(ATTR_SITE_ID, 'default'),
'customData': call.data.get(ATTR_CUSTOM_DATA, ''),
'init': {'type': 'action',
'text': call.data.get(ATTR_TEXT),
'canBeEnqueued': call.data.get(
ATTR_CAN_BE_ENQUEUED, True),
'intentFilter':
call.data.get(ATTR_INTENT_FILTER, [])}}
mqtt.async_publish(hass, 'hermes/dialogueManager/startSession',
json.dumps(notification))
return
hass.services.async_register(
DOMAIN, SERVICE_SAY, snips_say,
schema=SERVICE_SCHEMA_SAY)
hass.services.async_register(
DOMAIN, SERVICE_SAY_ACTION, snips_say_action,
schema=SERVICE_SCHEMA_SAY_ACTION)
return True return True

View file

@ -4,7 +4,10 @@ import json
from homeassistant.core import callback from homeassistant.core import callback
from homeassistant.bootstrap import async_setup_component from homeassistant.bootstrap import async_setup_component
from tests.common import async_fire_mqtt_message, async_mock_intent from tests.common import (async_fire_mqtt_message, async_mock_intent,
async_mock_service)
from homeassistant.components.snips import (SERVICE_SCHEMA_SAY,
SERVICE_SCHEMA_SAY_ACTION)
@asyncio.coroutine @asyncio.coroutine
@ -238,3 +241,66 @@ def test_snips_intent_username(hass, mqtt_mock):
intent = intents[0] intent = intents[0]
assert intent.platform == 'snips' assert intent.platform == 'snips'
assert intent.intent_type == 'Lights' assert intent.intent_type == 'Lights'
@asyncio.coroutine
def test_snips_say(hass, caplog):
"""Test snips say with invalid config."""
calls = async_mock_service(hass, 'snips', 'say',
SERVICE_SCHEMA_SAY)
data = {'text': 'Hello'}
yield from hass.services.async_call('snips', 'say', data)
yield from hass.async_block_till_done()
assert len(calls) == 1
assert calls[0].domain == 'snips'
assert calls[0].service == 'say'
assert calls[0].data['text'] == 'Hello'
@asyncio.coroutine
def test_snips_say_action(hass, caplog):
"""Test snips say_action with invalid config."""
calls = async_mock_service(hass, 'snips', 'say_action',
SERVICE_SCHEMA_SAY_ACTION)
data = {'text': 'Hello', 'intent_filter': ['myIntent']}
yield from hass.services.async_call('snips', 'say_action', data)
yield from hass.async_block_till_done()
assert len(calls) == 1
assert calls[0].domain == 'snips'
assert calls[0].service == 'say_action'
assert calls[0].data['text'] == 'Hello'
assert calls[0].data['intent_filter'] == ['myIntent']
@asyncio.coroutine
def test_snips_say_invalid_config(hass, caplog):
"""Test snips say with invalid config."""
calls = async_mock_service(hass, 'snips', 'say',
SERVICE_SCHEMA_SAY)
data = {'text': 'Hello', 'badKey': 'boo'}
yield from hass.services.async_call('snips', 'say', data)
yield from hass.async_block_till_done()
assert len(calls) == 0
assert 'ERROR' in caplog.text
assert 'Invalid service data' in caplog.text
@asyncio.coroutine
def test_snips_say_action_invalid_config(hass, caplog):
"""Test snips say_action with invalid config."""
calls = async_mock_service(hass, 'snips', 'say_action',
SERVICE_SCHEMA_SAY_ACTION)
data = {'text': 'Hello', 'can_be_enqueued': 'notabool'}
yield from hass.services.async_call('snips', 'say_action', data)
yield from hass.async_block_till_done()
assert len(calls) == 0
assert 'ERROR' in caplog.text
assert 'Invalid service data' in caplog.text