1 """Support for Snips on-device ASR and NLU."""
3 from datetime
import timedelta
7 import voluptuous
as vol
15 CONF_INTENTS =
"intents"
16 CONF_ACTION =
"action"
17 CONF_FEEDBACK =
"feedback_sounds"
18 CONF_PROBABILITY =
"probability_threshold"
19 CONF_SITE_IDS =
"site_ids"
22 SERVICE_SAY_ACTION =
"say_action"
23 SERVICE_FEEDBACK_ON =
"feedback_on"
24 SERVICE_FEEDBACK_OFF =
"feedback_off"
26 INTENT_TOPIC =
"hermes/intent/#"
27 FEEDBACK_ON_TOPIC =
"hermes/feedback/sound/toggleOn"
28 FEEDBACK_OFF_TOPIC =
"hermes/feedback/sound/toggleOff"
31 ATTR_SITE_ID =
"site_id"
32 ATTR_CUSTOM_DATA =
"custom_data"
33 ATTR_CAN_BE_ENQUEUED =
"can_be_enqueued"
34 ATTR_INTENT_FILTER =
"intent_filter"
36 _LOGGER = logging.getLogger(__name__)
38 CONFIG_SCHEMA = vol.Schema(
42 vol.Optional(CONF_FEEDBACK): cv.boolean,
43 vol.Optional(CONF_PROBABILITY, default=0): vol.Coerce(float),
44 vol.Optional(CONF_SITE_IDS, default=[
"default"]): vol.All(
45 cv.ensure_list, [cv.string]
50 extra=vol.ALLOW_EXTRA,
53 INTENT_SCHEMA = vol.Schema(
55 vol.Required(
"input"): str,
56 vol.Required(
"intent"): {vol.Required(
"intentName"): str},
57 vol.Optional(
"slots"): [
59 vol.Required(
"slotName"): str,
60 vol.Required(
"value"): {
61 vol.Required(
"kind"): str,
62 vol.Optional(
"value"): cv.match_all,
63 vol.Optional(
"rawValue"): cv.match_all,
68 extra=vol.ALLOW_EXTRA,
71 SERVICE_SCHEMA_SAY = vol.Schema(
73 vol.Required(ATTR_TEXT): str,
74 vol.Optional(ATTR_SITE_ID, default=
"default"): str,
75 vol.Optional(ATTR_CUSTOM_DATA, default=
""): str,
78 SERVICE_SCHEMA_SAY_ACTION = vol.Schema(
80 vol.Required(ATTR_TEXT): str,
81 vol.Optional(ATTR_SITE_ID, default=
"default"): str,
82 vol.Optional(ATTR_CUSTOM_DATA, default=
""): str,
83 vol.Optional(ATTR_CAN_BE_ENQUEUED, default=
True): cv.boolean,
84 vol.Optional(ATTR_INTENT_FILTER): vol.All(cv.ensure_list),
87 SERVICE_SCHEMA_FEEDBACK = vol.Schema(
88 {vol.Optional(ATTR_SITE_ID, default=
"default"): str}
92 async
def async_setup(hass: HomeAssistant, config: ConfigType) -> bool:
93 """Activate Snips component."""
96 if not await mqtt.async_wait_for_mqtt_client(hass):
97 _LOGGER.error(
"MQTT integration is not available")
100 async
def async_set_feedback(site_ids, state):
101 """Set Feedback sound state."""
102 site_ids = site_ids
if site_ids
else config[DOMAIN].
get(CONF_SITE_IDS)
103 topic = FEEDBACK_ON_TOPIC
if state
else FEEDBACK_OFF_TOPIC
104 for site_id
in site_ids:
105 payload = json.dumps({
"siteId": site_id})
106 await mqtt.async_publish(hass, FEEDBACK_ON_TOPIC,
"", qos=0, retain=
False)
107 await mqtt.async_publish(hass, topic, payload, qos=
int(state), retain=state)
109 if CONF_FEEDBACK
in config[DOMAIN]:
110 await async_set_feedback(
None, config[DOMAIN][CONF_FEEDBACK])
112 async
def message_received(msg):
113 """Handle new messages on MQTT."""
114 _LOGGER.debug(
"New intent: %s", msg.payload)
117 request = json.loads(msg.payload)
119 _LOGGER.error(
"Received invalid JSON: %s", msg.payload)
122 if request[
"intent"][
"confidenceScore"] < config[DOMAIN].
get(CONF_PROBABILITY):
124 "Intent below probaility threshold %s < %s",
125 request[
"intent"][
"confidenceScore"],
126 config[DOMAIN].
get(CONF_PROBABILITY),
132 except vol.Invalid
as err:
133 _LOGGER.error(
"Intent has invalid schema: %s. %s", err, request)
136 if request[
"intent"][
"intentName"].startswith(
"user_"):
137 intent_type = request[
"intent"][
"intentName"].split(
"__")[-1]
139 intent_type = request[
"intent"][
"intentName"].split(
":")[-1]
141 for slot
in request.get(
"slots", []):
143 slots[f
"{slot['slotName']}_raw"] = {
"value": slot[
"rawValue"]}
144 slots[
"site_id"] = {
"value": request.get(
"siteId")}
145 slots[
"session_id"] = {
"value": request.get(
"sessionId")}
146 slots[
"confidenceScore"] = {
"value": request[
"intent"][
"confidenceScore"]}
149 intent_response = await intent.async_handle(
150 hass, DOMAIN, intent_type, slots, request[
"input"]
152 notification = {
"sessionId": request.get(
"sessionId",
"default")}
154 if "plain" in intent_response.speech:
155 notification[
"text"] = intent_response.speech[
"plain"][
"speech"]
157 _LOGGER.debug(
"send_response %s", json.dumps(notification))
158 await mqtt.async_publish(
159 hass,
"hermes/dialogueManager/endSession", json.dumps(notification)
161 except intent.UnknownIntent:
163 "Received unknown intent %s", request[
"intent"][
"intentName"]
165 except intent.IntentError:
166 _LOGGER.exception(
"Error while handling intent: %s", intent_type)
168 await mqtt.async_subscribe(hass, INTENT_TOPIC, message_received)
170 async
def snips_say(call: ServiceCall) ->
None:
171 """Send a Snips notification message."""
173 "siteId": call.data.get(ATTR_SITE_ID,
"default"),
174 "customData": call.data.get(ATTR_CUSTOM_DATA,
""),
175 "init": {
"type":
"notification",
"text": call.data.get(ATTR_TEXT)},
177 await mqtt.async_publish(
178 hass,
"hermes/dialogueManager/startSession", json.dumps(notification)
181 async
def snips_say_action(call: ServiceCall) ->
None:
182 """Send a Snips action message."""
184 "siteId": call.data.get(ATTR_SITE_ID,
"default"),
185 "customData": call.data.get(ATTR_CUSTOM_DATA,
""),
188 "text": call.data.get(ATTR_TEXT),
189 "canBeEnqueued": call.data.get(ATTR_CAN_BE_ENQUEUED,
True),
190 "intentFilter": call.data.get(ATTR_INTENT_FILTER, []),
193 await mqtt.async_publish(
194 hass,
"hermes/dialogueManager/startSession", json.dumps(notification)
197 async
def feedback_on(call: ServiceCall) ->
None:
198 """Turn feedback sounds on."""
199 await async_set_feedback(call.data.get(ATTR_SITE_ID),
True)
201 async
def feedback_off(call: ServiceCall) ->
None:
202 """Turn feedback sounds off."""
203 await async_set_feedback(call.data.get(ATTR_SITE_ID),
False)
205 hass.services.async_register(
206 DOMAIN, SERVICE_SAY, snips_say, schema=SERVICE_SCHEMA_SAY
208 hass.services.async_register(
209 DOMAIN, SERVICE_SAY_ACTION, snips_say_action, schema=SERVICE_SCHEMA_SAY_ACTION
211 hass.services.async_register(
212 DOMAIN, SERVICE_FEEDBACK_ON, feedback_on, schema=SERVICE_SCHEMA_FEEDBACK
214 hass.services.async_register(
215 DOMAIN, SERVICE_FEEDBACK_OFF, feedback_off, schema=SERVICE_SCHEMA_FEEDBACK
222 """Convert snips builtin types to usable values."""
223 if "value" in slot[
"value"]:
224 value = slot[
"value"][
"value"]
226 value = slot[
"rawValue"]
228 if slot.get(
"entity") ==
"snips/duration":
230 weeks=slot[
"value"][
"weeks"],
231 days=slot[
"value"][
"days"],
232 hours=slot[
"value"][
"hours"],
233 minutes=slot[
"value"][
"minutes"],
234 seconds=slot[
"value"][
"seconds"],
236 value = delta.total_seconds()
web.Response get(self, web.Request request, str config_key)
bool async_setup(HomeAssistant hass, ConfigType config)
def resolve_slot_values(slot)