mirror of
https://github.com/home-assistant/core.git
synced 2025-12-24 21:06:19 +00:00
Remove snips integration (#155461)
This commit is contained in:
@@ -1,257 +0,0 @@
|
||||
"""Support for Snips on-device ASR and NLU."""
|
||||
|
||||
from datetime import timedelta
|
||||
import json
|
||||
import logging
|
||||
|
||||
import voluptuous as vol
|
||||
|
||||
from homeassistant.components import mqtt
|
||||
from homeassistant.core import (
|
||||
DOMAIN as HOMEASSISTANT_DOMAIN,
|
||||
HomeAssistant,
|
||||
ServiceCall,
|
||||
)
|
||||
from homeassistant.helpers import config_validation as cv, intent
|
||||
from homeassistant.helpers.issue_registry import IssueSeverity, async_create_issue
|
||||
from homeassistant.helpers.typing import ConfigType
|
||||
|
||||
DOMAIN = "snips"
|
||||
CONF_INTENTS = "intents"
|
||||
CONF_ACTION = "action"
|
||||
CONF_FEEDBACK = "feedback_sounds"
|
||||
CONF_PROBABILITY = "probability_threshold"
|
||||
CONF_SITE_IDS = "site_ids"
|
||||
|
||||
SERVICE_SAY = "say"
|
||||
SERVICE_SAY_ACTION = "say_action"
|
||||
SERVICE_FEEDBACK_ON = "feedback_on"
|
||||
SERVICE_FEEDBACK_OFF = "feedback_off"
|
||||
|
||||
INTENT_TOPIC = "hermes/intent/#"
|
||||
FEEDBACK_ON_TOPIC = "hermes/feedback/sound/toggleOn"
|
||||
FEEDBACK_OFF_TOPIC = "hermes/feedback/sound/toggleOff"
|
||||
|
||||
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__)
|
||||
|
||||
CONFIG_SCHEMA = vol.Schema(
|
||||
{
|
||||
DOMAIN: vol.Schema(
|
||||
{
|
||||
vol.Optional(CONF_FEEDBACK): cv.boolean,
|
||||
vol.Optional(CONF_PROBABILITY, default=0): vol.Coerce(float),
|
||||
vol.Optional(CONF_SITE_IDS, default=["default"]): vol.All(
|
||||
cv.ensure_list, [cv.string]
|
||||
),
|
||||
}
|
||||
)
|
||||
},
|
||||
extra=vol.ALLOW_EXTRA,
|
||||
)
|
||||
|
||||
INTENT_SCHEMA = vol.Schema(
|
||||
{
|
||||
vol.Required("input"): str,
|
||||
vol.Required("intent"): {vol.Required("intentName"): str},
|
||||
vol.Optional("slots"): [
|
||||
{
|
||||
vol.Required("slotName"): str,
|
||||
vol.Required("value"): {
|
||||
vol.Required("kind"): str,
|
||||
vol.Optional("value"): cv.match_all,
|
||||
vol.Optional("rawValue"): cv.match_all,
|
||||
},
|
||||
}
|
||||
],
|
||||
},
|
||||
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),
|
||||
}
|
||||
)
|
||||
SERVICE_SCHEMA_FEEDBACK = vol.Schema(
|
||||
{vol.Optional(ATTR_SITE_ID, default="default"): str}
|
||||
)
|
||||
|
||||
|
||||
async def async_setup(hass: HomeAssistant, config: ConfigType) -> bool:
|
||||
"""Activate Snips component."""
|
||||
async_create_issue(
|
||||
hass,
|
||||
HOMEASSISTANT_DOMAIN,
|
||||
f"deprecated_system_packages_yaml_integration_{DOMAIN}",
|
||||
breaks_in_ha_version="2025.12.0",
|
||||
is_fixable=False,
|
||||
issue_domain=DOMAIN,
|
||||
severity=IssueSeverity.WARNING,
|
||||
translation_key="deprecated_system_packages_yaml_integration",
|
||||
translation_placeholders={
|
||||
"domain": DOMAIN,
|
||||
"integration_title": "Snips",
|
||||
},
|
||||
)
|
||||
|
||||
# Make sure MQTT integration is enabled and the client is available
|
||||
if not await mqtt.async_wait_for_mqtt_client(hass):
|
||||
_LOGGER.error("MQTT integration is not available")
|
||||
return False
|
||||
|
||||
async def async_set_feedback(site_ids, state):
|
||||
"""Set Feedback sound state."""
|
||||
site_ids = site_ids if site_ids else config[DOMAIN].get(CONF_SITE_IDS)
|
||||
topic = FEEDBACK_ON_TOPIC if state else FEEDBACK_OFF_TOPIC
|
||||
for site_id in site_ids:
|
||||
payload = json.dumps({"siteId": site_id})
|
||||
await mqtt.async_publish(hass, FEEDBACK_ON_TOPIC, "", qos=0, retain=False)
|
||||
await mqtt.async_publish(hass, topic, payload, qos=int(state), retain=state)
|
||||
|
||||
if CONF_FEEDBACK in config[DOMAIN]:
|
||||
await async_set_feedback(None, config[DOMAIN][CONF_FEEDBACK])
|
||||
|
||||
async def message_received(msg):
|
||||
"""Handle new messages on MQTT."""
|
||||
_LOGGER.debug("New intent: %s", msg.payload)
|
||||
|
||||
try:
|
||||
request = json.loads(msg.payload)
|
||||
except TypeError:
|
||||
_LOGGER.error("Received invalid JSON: %s", msg.payload)
|
||||
return
|
||||
|
||||
if request["intent"]["confidenceScore"] < config[DOMAIN].get(CONF_PROBABILITY):
|
||||
_LOGGER.warning(
|
||||
"Intent below probaility threshold %s < %s",
|
||||
request["intent"]["confidenceScore"],
|
||||
config[DOMAIN].get(CONF_PROBABILITY),
|
||||
)
|
||||
return
|
||||
|
||||
try:
|
||||
request = INTENT_SCHEMA(request)
|
||||
except vol.Invalid as err:
|
||||
_LOGGER.error("Intent has invalid schema: %s. %s", err, request)
|
||||
return
|
||||
|
||||
if request["intent"]["intentName"].startswith("user_"):
|
||||
intent_type = request["intent"]["intentName"].split("__")[-1]
|
||||
else:
|
||||
intent_type = request["intent"]["intentName"].split(":")[-1]
|
||||
slots = {}
|
||||
for slot in request.get("slots", []):
|
||||
slots[slot["slotName"]] = {"value": resolve_slot_values(slot)}
|
||||
slots[f"{slot['slotName']}_raw"] = {"value": slot["rawValue"]}
|
||||
slots["site_id"] = {"value": request.get("siteId")}
|
||||
slots["session_id"] = {"value": request.get("sessionId")}
|
||||
slots["confidenceScore"] = {"value": request["intent"]["confidenceScore"]}
|
||||
|
||||
try:
|
||||
intent_response = await intent.async_handle(
|
||||
hass, DOMAIN, intent_type, slots, request["input"]
|
||||
)
|
||||
notification = {"sessionId": request.get("sessionId", "default")}
|
||||
|
||||
if "plain" in intent_response.speech:
|
||||
notification["text"] = intent_response.speech["plain"]["speech"]
|
||||
|
||||
_LOGGER.debug("send_response %s", json.dumps(notification))
|
||||
await mqtt.async_publish(
|
||||
hass, "hermes/dialogueManager/endSession", json.dumps(notification)
|
||||
)
|
||||
except intent.UnknownIntent:
|
||||
_LOGGER.warning(
|
||||
"Received unknown intent %s", request["intent"]["intentName"]
|
||||
)
|
||||
except intent.IntentError:
|
||||
_LOGGER.exception("Error while handling intent: %s", intent_type)
|
||||
|
||||
await mqtt.async_subscribe(hass, INTENT_TOPIC, message_received)
|
||||
|
||||
async def snips_say(call: ServiceCall) -> None:
|
||||
"""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)},
|
||||
}
|
||||
await mqtt.async_publish(
|
||||
hass, "hermes/dialogueManager/startSession", json.dumps(notification)
|
||||
)
|
||||
|
||||
async def snips_say_action(call: ServiceCall) -> None:
|
||||
"""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, []),
|
||||
},
|
||||
}
|
||||
await mqtt.async_publish(
|
||||
hass, "hermes/dialogueManager/startSession", json.dumps(notification)
|
||||
)
|
||||
|
||||
async def feedback_on(call: ServiceCall) -> None:
|
||||
"""Turn feedback sounds on."""
|
||||
await async_set_feedback(call.data.get(ATTR_SITE_ID), True)
|
||||
|
||||
async def feedback_off(call: ServiceCall) -> None:
|
||||
"""Turn feedback sounds off."""
|
||||
await async_set_feedback(call.data.get(ATTR_SITE_ID), False)
|
||||
|
||||
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
|
||||
)
|
||||
hass.services.async_register(
|
||||
DOMAIN, SERVICE_FEEDBACK_ON, feedback_on, schema=SERVICE_SCHEMA_FEEDBACK
|
||||
)
|
||||
hass.services.async_register(
|
||||
DOMAIN, SERVICE_FEEDBACK_OFF, feedback_off, schema=SERVICE_SCHEMA_FEEDBACK
|
||||
)
|
||||
|
||||
return True
|
||||
|
||||
|
||||
def resolve_slot_values(slot):
|
||||
"""Convert snips builtin types to usable values."""
|
||||
if "value" in slot["value"]:
|
||||
value = slot["value"]["value"]
|
||||
else:
|
||||
value = slot["rawValue"]
|
||||
|
||||
if slot.get("entity") == "snips/duration":
|
||||
delta = timedelta(
|
||||
weeks=slot["value"]["weeks"],
|
||||
days=slot["value"]["days"],
|
||||
hours=slot["value"]["hours"],
|
||||
minutes=slot["value"]["minutes"],
|
||||
seconds=slot["value"]["seconds"],
|
||||
)
|
||||
value = delta.total_seconds()
|
||||
|
||||
return value
|
||||
@@ -1,16 +0,0 @@
|
||||
{
|
||||
"services": {
|
||||
"feedback_off": {
|
||||
"service": "mdi:message-alert"
|
||||
},
|
||||
"feedback_on": {
|
||||
"service": "mdi:message-alert"
|
||||
},
|
||||
"say": {
|
||||
"service": "mdi:chat"
|
||||
},
|
||||
"say_action": {
|
||||
"service": "mdi:account-voice"
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,9 +0,0 @@
|
||||
{
|
||||
"domain": "snips",
|
||||
"name": "Snips",
|
||||
"codeowners": [],
|
||||
"dependencies": ["mqtt"],
|
||||
"documentation": "https://www.home-assistant.io/integrations/snips",
|
||||
"iot_class": "local_push",
|
||||
"quality_scale": "legacy"
|
||||
}
|
||||
@@ -1,56 +0,0 @@
|
||||
feedback_off:
|
||||
fields:
|
||||
site_id:
|
||||
example: bedroom
|
||||
default: default
|
||||
selector:
|
||||
text:
|
||||
feedback_on:
|
||||
fields:
|
||||
site_id:
|
||||
example: bedroom
|
||||
default: default
|
||||
selector:
|
||||
text:
|
||||
say:
|
||||
fields:
|
||||
custom_data:
|
||||
example: user=UserName
|
||||
default: ""
|
||||
selector:
|
||||
text:
|
||||
site_id:
|
||||
example: bedroom
|
||||
default: default
|
||||
selector:
|
||||
text:
|
||||
text:
|
||||
required: true
|
||||
example: My name is snips
|
||||
selector:
|
||||
text:
|
||||
say_action:
|
||||
fields:
|
||||
can_be_enqueued:
|
||||
default: true
|
||||
selector:
|
||||
boolean:
|
||||
custom_data:
|
||||
example: user=UserName
|
||||
default: ""
|
||||
selector:
|
||||
text:
|
||||
intent_filter:
|
||||
example: "turnOnLights, turnOffLights"
|
||||
selector:
|
||||
object:
|
||||
site_id:
|
||||
example: bedroom
|
||||
default: default
|
||||
selector:
|
||||
text:
|
||||
text:
|
||||
required: true
|
||||
example: My name is snips
|
||||
selector:
|
||||
text:
|
||||
@@ -1,68 +0,0 @@
|
||||
{
|
||||
"services": {
|
||||
"feedback_off": {
|
||||
"description": "Turns feedback sounds off.",
|
||||
"fields": {
|
||||
"site_id": {
|
||||
"description": "Site to turn sounds on, defaults to all sites.",
|
||||
"name": "Site ID"
|
||||
}
|
||||
},
|
||||
"name": "Feedback off"
|
||||
},
|
||||
"feedback_on": {
|
||||
"description": "Turns feedback sounds on.",
|
||||
"fields": {
|
||||
"site_id": {
|
||||
"description": "[%key:component::snips::services::feedback_off::fields::site_id::description%]",
|
||||
"name": "Site ID"
|
||||
}
|
||||
},
|
||||
"name": "Feedback on"
|
||||
},
|
||||
"say": {
|
||||
"description": "Sends a TTS message to Snips.",
|
||||
"fields": {
|
||||
"custom_data": {
|
||||
"description": "Custom data that will be included with all messages in this session.",
|
||||
"name": "Custom data"
|
||||
},
|
||||
"site_id": {
|
||||
"description": "Site to use to start session, defaults to default.",
|
||||
"name": "Site ID"
|
||||
},
|
||||
"text": {
|
||||
"description": "Text to say.",
|
||||
"name": "Text"
|
||||
}
|
||||
},
|
||||
"name": "Say"
|
||||
},
|
||||
"say_action": {
|
||||
"description": "Sends a TTS message to Snips to listen for a response.",
|
||||
"fields": {
|
||||
"can_be_enqueued": {
|
||||
"description": "Whether the session should wait for an open session to end. Otherwise it is dropped if another session is already running.",
|
||||
"name": "Can be enqueued"
|
||||
},
|
||||
"custom_data": {
|
||||
"description": "[%key:component::snips::services::say::fields::custom_data::description%]",
|
||||
"name": "[%key:component::snips::services::say::fields::custom_data::name%]"
|
||||
},
|
||||
"intent_filter": {
|
||||
"description": "Optional Array of Strings - A list of intents names to restrict the NLU resolution to on the first query.",
|
||||
"name": "Intent filter"
|
||||
},
|
||||
"site_id": {
|
||||
"description": "[%key:component::snips::services::say::fields::site_id::description%]",
|
||||
"name": "Site ID"
|
||||
},
|
||||
"text": {
|
||||
"description": "[%key:component::snips::services::say::fields::text::description%]",
|
||||
"name": "Text"
|
||||
}
|
||||
},
|
||||
"name": "Say action"
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -6228,12 +6228,6 @@
|
||||
"config_flow": true,
|
||||
"iot_class": "local_push"
|
||||
},
|
||||
"snips": {
|
||||
"name": "Snips",
|
||||
"integration_type": "hub",
|
||||
"config_flow": false,
|
||||
"iot_class": "local_push"
|
||||
},
|
||||
"snmp": {
|
||||
"name": "SNMP",
|
||||
"integration_type": "hub",
|
||||
|
||||
@@ -902,7 +902,6 @@ INTEGRATIONS_WITHOUT_QUALITY_SCALE_FILE = [
|
||||
"sms",
|
||||
"smtp",
|
||||
"snapcast",
|
||||
"snips",
|
||||
"snmp",
|
||||
"snooz",
|
||||
"solaredge",
|
||||
@@ -1939,7 +1938,6 @@ INTEGRATIONS_WITHOUT_SCALE = [
|
||||
"sms",
|
||||
"smtp",
|
||||
"snapcast",
|
||||
"snips",
|
||||
"snmp",
|
||||
"snooz",
|
||||
"solaredge",
|
||||
|
||||
@@ -1 +0,0 @@
|
||||
"""Tests for the snips component."""
|
||||
@@ -1,559 +0,0 @@
|
||||
"""Test the Snips component."""
|
||||
|
||||
import json
|
||||
import logging
|
||||
|
||||
import pytest
|
||||
import voluptuous as vol
|
||||
|
||||
from homeassistant.components import snips
|
||||
from homeassistant.core import DOMAIN as HOMEASSISTANT_DOMAIN, HomeAssistant
|
||||
from homeassistant.helpers import issue_registry as ir
|
||||
from homeassistant.helpers.intent import ServiceIntentHandler, async_register
|
||||
from homeassistant.setup import async_setup_component
|
||||
|
||||
from tests.common import async_fire_mqtt_message, async_mock_intent, async_mock_service
|
||||
from tests.typing import MqttMockHAClient
|
||||
|
||||
|
||||
async def test_snips_config(
|
||||
hass: HomeAssistant,
|
||||
mqtt_mock: MqttMockHAClient,
|
||||
issue_registry: ir.IssueRegistry,
|
||||
) -> None:
|
||||
"""Test Snips Config."""
|
||||
assert await async_setup_component(
|
||||
hass,
|
||||
"snips",
|
||||
{
|
||||
"snips": {
|
||||
"feedback_sounds": True,
|
||||
"probability_threshold": 0.5,
|
||||
"site_ids": ["default", "remote"],
|
||||
}
|
||||
},
|
||||
)
|
||||
assert (
|
||||
HOMEASSISTANT_DOMAIN,
|
||||
f"deprecated_system_packages_yaml_integration_{snips.DOMAIN}",
|
||||
) in issue_registry.issues
|
||||
|
||||
|
||||
async def test_snips_no_mqtt(
|
||||
hass: HomeAssistant, caplog: pytest.LogCaptureFixture
|
||||
) -> None:
|
||||
"""Test Snips Config."""
|
||||
result = await async_setup_component(
|
||||
hass,
|
||||
"snips",
|
||||
{
|
||||
"snips": {
|
||||
"feedback_sounds": True,
|
||||
"probability_threshold": 0.5,
|
||||
"site_ids": ["default", "remote"],
|
||||
}
|
||||
},
|
||||
)
|
||||
assert not result
|
||||
assert "MQTT integration is not available" in caplog.text
|
||||
|
||||
|
||||
async def test_snips_bad_config(
|
||||
hass: HomeAssistant, mqtt_mock: MqttMockHAClient
|
||||
) -> None:
|
||||
"""Test Snips bad config."""
|
||||
result = await async_setup_component(
|
||||
hass,
|
||||
"snips",
|
||||
{
|
||||
"snips": {
|
||||
"feedback_sounds": "on",
|
||||
"probability": "none",
|
||||
"site_ids": "default",
|
||||
}
|
||||
},
|
||||
)
|
||||
assert not result
|
||||
|
||||
|
||||
async def test_snips_config_feedback_on(
|
||||
hass: HomeAssistant, mqtt_mock: MqttMockHAClient
|
||||
) -> None:
|
||||
"""Test Snips Config."""
|
||||
result = await async_setup_component(
|
||||
hass, "snips", {"snips": {"feedback_sounds": True}}
|
||||
)
|
||||
assert result
|
||||
await hass.async_block_till_done()
|
||||
|
||||
assert mqtt_mock.async_publish.call_count == 2
|
||||
topic = mqtt_mock.async_publish.call_args_list[0][0][0]
|
||||
assert topic == "hermes/feedback/sound/toggleOn"
|
||||
topic = mqtt_mock.async_publish.call_args_list[1][0][0]
|
||||
assert topic == "hermes/feedback/sound/toggleOn"
|
||||
assert mqtt_mock.async_publish.call_args_list[1][0][2] == 1
|
||||
assert mqtt_mock.async_publish.call_args_list[1][0][3]
|
||||
|
||||
|
||||
async def test_snips_config_feedback_off(
|
||||
hass: HomeAssistant, mqtt_mock: MqttMockHAClient
|
||||
) -> None:
|
||||
"""Test Snips Config."""
|
||||
result = await async_setup_component(
|
||||
hass, "snips", {"snips": {"feedback_sounds": False}}
|
||||
)
|
||||
assert result
|
||||
await hass.async_block_till_done()
|
||||
|
||||
assert mqtt_mock.async_publish.call_count == 2
|
||||
topic = mqtt_mock.async_publish.call_args_list[0][0][0]
|
||||
assert topic == "hermes/feedback/sound/toggleOn"
|
||||
topic = mqtt_mock.async_publish.call_args_list[1][0][0]
|
||||
assert topic == "hermes/feedback/sound/toggleOff"
|
||||
assert mqtt_mock.async_publish.call_args_list[1][0][2] == 0
|
||||
assert not mqtt_mock.async_publish.call_args_list[1][0][3]
|
||||
|
||||
|
||||
async def test_snips_config_no_feedback(
|
||||
hass: HomeAssistant, mqtt_mock: MqttMockHAClient
|
||||
) -> None:
|
||||
"""Test Snips Config."""
|
||||
calls = async_mock_service(hass, "snips", "say")
|
||||
result = await async_setup_component(hass, "snips", {"snips": {}})
|
||||
assert result
|
||||
await hass.async_block_till_done()
|
||||
assert len(calls) == 0
|
||||
|
||||
|
||||
async def test_snips_intent(hass: HomeAssistant, mqtt_mock: MqttMockHAClient) -> None:
|
||||
"""Test intent via Snips."""
|
||||
result = await async_setup_component(hass, "snips", {"snips": {}})
|
||||
assert result
|
||||
payload = """
|
||||
{
|
||||
"siteId": "default",
|
||||
"sessionId": "1234567890ABCDEF",
|
||||
"input": "turn the lights green",
|
||||
"intent": {
|
||||
"intentName": "Lights",
|
||||
"confidenceScore": 1
|
||||
},
|
||||
"slots": [
|
||||
{
|
||||
"slotName": "light_color",
|
||||
"value": {
|
||||
"kind": "Custom",
|
||||
"value": "green"
|
||||
},
|
||||
"rawValue": "green"
|
||||
}
|
||||
]
|
||||
}
|
||||
"""
|
||||
|
||||
intents = async_mock_intent(hass, "Lights")
|
||||
|
||||
async_fire_mqtt_message(hass, "hermes/intent/Lights", payload)
|
||||
await hass.async_block_till_done()
|
||||
assert len(intents) == 1
|
||||
intent = intents[0]
|
||||
assert intent.platform == "snips"
|
||||
assert intent.intent_type == "Lights"
|
||||
assert intent
|
||||
assert intent.slots == {
|
||||
"light_color": {"value": "green"},
|
||||
"light_color_raw": {"value": "green"},
|
||||
"confidenceScore": {"value": 1},
|
||||
"site_id": {"value": "default"},
|
||||
"session_id": {"value": "1234567890ABCDEF"},
|
||||
}
|
||||
assert intent.text_input == "turn the lights green"
|
||||
|
||||
|
||||
async def test_snips_service_intent(
|
||||
hass: HomeAssistant, mqtt_mock: MqttMockHAClient
|
||||
) -> None:
|
||||
"""Test ServiceIntentHandler via Snips."""
|
||||
hass.states.async_set("light.kitchen", "off")
|
||||
calls = async_mock_service(hass, "light", "turn_on")
|
||||
result = await async_setup_component(hass, "snips", {"snips": {}})
|
||||
assert result
|
||||
payload = """
|
||||
{
|
||||
"input": "turn the light on",
|
||||
"intent": {
|
||||
"intentName": "Lights",
|
||||
"confidenceScore": 0.85
|
||||
},
|
||||
"siteId": "default",
|
||||
"slots": [
|
||||
{
|
||||
"slotName": "name",
|
||||
"value": {
|
||||
"kind": "Custom",
|
||||
"value": "kitchen"
|
||||
},
|
||||
"rawValue": "green"
|
||||
}
|
||||
]
|
||||
}
|
||||
"""
|
||||
|
||||
async_register(
|
||||
hass, ServiceIntentHandler("Lights", "light", "turn_on", "Turned {} on")
|
||||
)
|
||||
|
||||
async_fire_mqtt_message(hass, "hermes/intent/Lights", payload)
|
||||
await hass.async_block_till_done()
|
||||
|
||||
assert len(calls) == 1
|
||||
assert calls[0].domain == "light"
|
||||
assert calls[0].service == "turn_on"
|
||||
assert calls[0].data["entity_id"] == "light.kitchen"
|
||||
assert "confidenceScore" not in calls[0].data
|
||||
assert "site_id" not in calls[0].data
|
||||
|
||||
|
||||
async def test_snips_intent_with_duration(
|
||||
hass: HomeAssistant, mqtt_mock: MqttMockHAClient
|
||||
) -> None:
|
||||
"""Test intent with Snips duration."""
|
||||
result = await async_setup_component(hass, "snips", {"snips": {}})
|
||||
assert result
|
||||
payload = """
|
||||
{
|
||||
"input": "set a timer of five minutes",
|
||||
"intent": {
|
||||
"intentName": "SetTimer",
|
||||
"confidenceScore": 1
|
||||
},
|
||||
"slots": [
|
||||
{
|
||||
"rawValue": "five minutes",
|
||||
"value": {
|
||||
"kind": "Duration",
|
||||
"years": 0,
|
||||
"quarters": 0,
|
||||
"months": 0,
|
||||
"weeks": 0,
|
||||
"days": 0,
|
||||
"hours": 0,
|
||||
"minutes": 5,
|
||||
"seconds": 0,
|
||||
"precision": "Exact"
|
||||
},
|
||||
"range": {
|
||||
"start": 15,
|
||||
"end": 27
|
||||
},
|
||||
"entity": "snips/duration",
|
||||
"slotName": "timer_duration"
|
||||
}
|
||||
]
|
||||
}
|
||||
"""
|
||||
intents = async_mock_intent(hass, "SetTimer")
|
||||
|
||||
async_fire_mqtt_message(hass, "hermes/intent/SetTimer", payload)
|
||||
await hass.async_block_till_done()
|
||||
assert len(intents) == 1
|
||||
intent = intents[0]
|
||||
assert intent.platform == "snips"
|
||||
assert intent.intent_type == "SetTimer"
|
||||
assert intent.slots == {
|
||||
"confidenceScore": {"value": 1},
|
||||
"site_id": {"value": None},
|
||||
"session_id": {"value": None},
|
||||
"timer_duration": {"value": 300},
|
||||
"timer_duration_raw": {"value": "five minutes"},
|
||||
}
|
||||
|
||||
|
||||
async def test_intent_speech_response(
|
||||
hass: HomeAssistant, mqtt_mock: MqttMockHAClient
|
||||
) -> None:
|
||||
"""Test intent speech response via Snips."""
|
||||
result = await async_setup_component(hass, "snips", {"snips": {}})
|
||||
assert result
|
||||
result = await async_setup_component(
|
||||
hass,
|
||||
"intent_script",
|
||||
{
|
||||
"intent_script": {
|
||||
"spokenIntent": {
|
||||
"speech": {"type": "plain", "text": "I am speaking to you"}
|
||||
}
|
||||
}
|
||||
},
|
||||
)
|
||||
assert result
|
||||
payload = """
|
||||
{
|
||||
"input": "speak to me",
|
||||
"sessionId": "abcdef0123456789",
|
||||
"intent": {
|
||||
"intentName": "spokenIntent",
|
||||
"confidenceScore": 1
|
||||
},
|
||||
"slots": []
|
||||
}
|
||||
"""
|
||||
async_fire_mqtt_message(hass, "hermes/intent/spokenIntent", payload)
|
||||
await hass.async_block_till_done()
|
||||
|
||||
assert mqtt_mock.async_publish.call_count == 1
|
||||
payload = json.loads(mqtt_mock.async_publish.call_args[0][1])
|
||||
topic = mqtt_mock.async_publish.call_args[0][0]
|
||||
assert payload["sessionId"] == "abcdef0123456789"
|
||||
assert payload["text"] == "I am speaking to you"
|
||||
assert topic == "hermes/dialogueManager/endSession"
|
||||
|
||||
|
||||
async def test_unknown_intent(
|
||||
hass: HomeAssistant, caplog: pytest.LogCaptureFixture, mqtt_mock: MqttMockHAClient
|
||||
) -> None:
|
||||
"""Test unknown intent."""
|
||||
caplog.set_level(logging.WARNING)
|
||||
result = await async_setup_component(hass, "snips", {"snips": {}})
|
||||
assert result
|
||||
payload = """
|
||||
{
|
||||
"input": "I don't know what I am supposed to do",
|
||||
"sessionId": "abcdef1234567890",
|
||||
"intent": {
|
||||
"intentName": "unknownIntent",
|
||||
"confidenceScore": 1
|
||||
},
|
||||
"slots": []
|
||||
}
|
||||
"""
|
||||
async_fire_mqtt_message(hass, "hermes/intent/unknownIntent", payload)
|
||||
await hass.async_block_till_done()
|
||||
assert "Received unknown intent unknownIntent" in caplog.text
|
||||
|
||||
|
||||
async def test_snips_intent_user(
|
||||
hass: HomeAssistant, mqtt_mock: MqttMockHAClient
|
||||
) -> None:
|
||||
"""Test intentName format user_XXX__intentName."""
|
||||
result = await async_setup_component(hass, "snips", {"snips": {}})
|
||||
assert result
|
||||
payload = """
|
||||
{
|
||||
"input": "what to do",
|
||||
"intent": {
|
||||
"intentName": "user_ABCDEF123__Lights",
|
||||
"confidenceScore": 1
|
||||
},
|
||||
"slots": []
|
||||
}
|
||||
"""
|
||||
intents = async_mock_intent(hass, "Lights")
|
||||
async_fire_mqtt_message(hass, "hermes/intent/user_ABCDEF123__Lights", payload)
|
||||
await hass.async_block_till_done()
|
||||
|
||||
assert len(intents) == 1
|
||||
intent = intents[0]
|
||||
assert intent.platform == "snips"
|
||||
assert intent.intent_type == "Lights"
|
||||
|
||||
|
||||
async def test_snips_intent_username(
|
||||
hass: HomeAssistant, mqtt_mock: MqttMockHAClient
|
||||
) -> None:
|
||||
"""Test intentName format username:intentName."""
|
||||
result = await async_setup_component(hass, "snips", {"snips": {}})
|
||||
assert result
|
||||
payload = """
|
||||
{
|
||||
"input": "what to do",
|
||||
"intent": {
|
||||
"intentName": "username:Lights",
|
||||
"confidenceScore": 1
|
||||
},
|
||||
"slots": []
|
||||
}
|
||||
"""
|
||||
intents = async_mock_intent(hass, "Lights")
|
||||
async_fire_mqtt_message(hass, "hermes/intent/username:Lights", payload)
|
||||
await hass.async_block_till_done()
|
||||
|
||||
assert len(intents) == 1
|
||||
intent = intents[0]
|
||||
assert intent.platform == "snips"
|
||||
assert intent.intent_type == "Lights"
|
||||
|
||||
|
||||
async def test_snips_low_probability(
|
||||
hass: HomeAssistant, caplog: pytest.LogCaptureFixture, mqtt_mock: MqttMockHAClient
|
||||
) -> None:
|
||||
"""Test intent via Snips."""
|
||||
caplog.set_level(logging.WARNING)
|
||||
result = await async_setup_component(
|
||||
hass, "snips", {"snips": {"probability_threshold": 0.5}}
|
||||
)
|
||||
assert result
|
||||
payload = """
|
||||
{
|
||||
"input": "I am not sure what to say",
|
||||
"intent": {
|
||||
"intentName": "LightsMaybe",
|
||||
"confidenceScore": 0.49
|
||||
},
|
||||
"slots": []
|
||||
}
|
||||
"""
|
||||
|
||||
async_mock_intent(hass, "LightsMaybe")
|
||||
async_fire_mqtt_message(hass, "hermes/intent/LightsMaybe", payload)
|
||||
await hass.async_block_till_done()
|
||||
assert "Intent below probaility threshold 0.49 < 0.5" in caplog.text
|
||||
|
||||
|
||||
async def test_intent_special_slots(
|
||||
hass: HomeAssistant, mqtt_mock: MqttMockHAClient
|
||||
) -> None:
|
||||
"""Test intent special slot values via Snips."""
|
||||
calls = async_mock_service(hass, "light", "turn_on")
|
||||
result = await async_setup_component(hass, "snips", {"snips": {}})
|
||||
assert result
|
||||
result = await async_setup_component(
|
||||
hass,
|
||||
"intent_script",
|
||||
{
|
||||
"intent_script": {
|
||||
"Lights": {
|
||||
"action": {
|
||||
"service": "light.turn_on",
|
||||
"data_template": {
|
||||
"confidenceScore": "{{ confidenceScore }}",
|
||||
"site_id": "{{ site_id }}",
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
)
|
||||
assert result
|
||||
payload = """
|
||||
{
|
||||
"input": "turn the light on",
|
||||
"intent": {
|
||||
"intentName": "Lights",
|
||||
"confidenceScore": 0.85
|
||||
},
|
||||
"siteId": "default",
|
||||
"slots": []
|
||||
}
|
||||
"""
|
||||
async_fire_mqtt_message(hass, "hermes/intent/Lights", payload)
|
||||
await hass.async_block_till_done()
|
||||
|
||||
assert len(calls) == 1
|
||||
assert calls[0].domain == "light"
|
||||
assert calls[0].service == "turn_on"
|
||||
assert calls[0].data["confidenceScore"] == 0.85
|
||||
assert calls[0].data["site_id"] == "default"
|
||||
|
||||
|
||||
async def test_snips_say(hass: HomeAssistant) -> None:
|
||||
"""Test snips say with invalid config."""
|
||||
calls = async_mock_service(hass, "snips", "say", snips.SERVICE_SCHEMA_SAY)
|
||||
data = {"text": "Hello"}
|
||||
await hass.services.async_call("snips", "say", data)
|
||||
await 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"
|
||||
|
||||
|
||||
async def test_snips_say_action(hass: HomeAssistant) -> None:
|
||||
"""Test snips say_action with invalid config."""
|
||||
calls = async_mock_service(
|
||||
hass, "snips", "say_action", snips.SERVICE_SCHEMA_SAY_ACTION
|
||||
)
|
||||
|
||||
data = {"text": "Hello", "intent_filter": ["myIntent"]}
|
||||
await hass.services.async_call("snips", "say_action", data)
|
||||
await 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"]
|
||||
|
||||
|
||||
async def test_snips_say_invalid_config(hass: HomeAssistant) -> None:
|
||||
"""Test snips say with invalid config."""
|
||||
calls = async_mock_service(hass, "snips", "say", snips.SERVICE_SCHEMA_SAY)
|
||||
|
||||
data = {"text": "Hello", "badKey": "boo"}
|
||||
with pytest.raises(vol.Invalid):
|
||||
await hass.services.async_call("snips", "say", data)
|
||||
await hass.async_block_till_done()
|
||||
|
||||
assert len(calls) == 0
|
||||
|
||||
|
||||
async def test_snips_say_action_invalid(hass: HomeAssistant) -> None:
|
||||
"""Test snips say_action with invalid config."""
|
||||
calls = async_mock_service(
|
||||
hass, "snips", "say_action", snips.SERVICE_SCHEMA_SAY_ACTION
|
||||
)
|
||||
|
||||
data = {"text": "Hello", "can_be_enqueued": "notabool"}
|
||||
|
||||
with pytest.raises(vol.Invalid):
|
||||
await hass.services.async_call("snips", "say_action", data)
|
||||
await hass.async_block_till_done()
|
||||
|
||||
assert len(calls) == 0
|
||||
|
||||
|
||||
async def test_snips_feedback_on(hass: HomeAssistant) -> None:
|
||||
"""Test snips say with invalid config."""
|
||||
calls = async_mock_service(
|
||||
hass, "snips", "feedback_on", snips.SERVICE_SCHEMA_FEEDBACK
|
||||
)
|
||||
|
||||
data = {"site_id": "remote"}
|
||||
await hass.services.async_call("snips", "feedback_on", data)
|
||||
await hass.async_block_till_done()
|
||||
|
||||
assert len(calls) == 1
|
||||
assert calls[0].domain == "snips"
|
||||
assert calls[0].service == "feedback_on"
|
||||
assert calls[0].data["site_id"] == "remote"
|
||||
|
||||
|
||||
async def test_snips_feedback_off(hass: HomeAssistant) -> None:
|
||||
"""Test snips say with invalid config."""
|
||||
calls = async_mock_service(
|
||||
hass, "snips", "feedback_off", snips.SERVICE_SCHEMA_FEEDBACK
|
||||
)
|
||||
|
||||
data = {"site_id": "remote"}
|
||||
await hass.services.async_call("snips", "feedback_off", data)
|
||||
await hass.async_block_till_done()
|
||||
|
||||
assert len(calls) == 1
|
||||
assert calls[0].domain == "snips"
|
||||
assert calls[0].service == "feedback_off"
|
||||
assert calls[0].data["site_id"] == "remote"
|
||||
|
||||
|
||||
async def test_snips_feedback_config(hass: HomeAssistant) -> None:
|
||||
"""Test snips say with invalid config."""
|
||||
calls = async_mock_service(
|
||||
hass, "snips", "feedback_on", snips.SERVICE_SCHEMA_FEEDBACK
|
||||
)
|
||||
|
||||
data = {"site_id": "remote", "test": "test"}
|
||||
with pytest.raises(vol.Invalid):
|
||||
await hass.services.async_call("snips", "feedback_on", data)
|
||||
await hass.async_block_till_done()
|
||||
|
||||
assert len(calls) == 0
|
||||
Reference in New Issue
Block a user