mirror of
https://github.com/home-assistant/core.git
synced 2025-07-20 11:47:06 +00:00
Merge pull request #57 from balloob/component-conversation
Add conversation component
This commit is contained in:
commit
c46e27928f
72
homeassistant/components/conversation.py
Normal file
72
homeassistant/components/conversation.py
Normal file
@ -0,0 +1,72 @@
|
|||||||
|
"""
|
||||||
|
homeassistant.components.conversation
|
||||||
|
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||||
|
|
||||||
|
Provides functionality to have conversations with Home Assistant.
|
||||||
|
This is more a proof of concept.
|
||||||
|
"""
|
||||||
|
import logging
|
||||||
|
import re
|
||||||
|
|
||||||
|
import homeassistant
|
||||||
|
from homeassistant.const import (
|
||||||
|
ATTR_FRIENDLY_NAME, ATTR_ENTITY_ID, SERVICE_TURN_ON, SERVICE_TURN_OFF)
|
||||||
|
|
||||||
|
DOMAIN = "conversation"
|
||||||
|
DEPENDENCIES = []
|
||||||
|
|
||||||
|
SERVICE_PROCESS = "process"
|
||||||
|
|
||||||
|
ATTR_TEXT = "text"
|
||||||
|
|
||||||
|
REGEX_TURN_COMMAND = re.compile(r'turn (?P<name>(?: |\w)+) (?P<command>\w+)')
|
||||||
|
|
||||||
|
|
||||||
|
def setup(hass, config):
|
||||||
|
""" Registers the process service. """
|
||||||
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
def process(service):
|
||||||
|
""" Parses text into commands for Home Assistant. """
|
||||||
|
if ATTR_TEXT not in service.data:
|
||||||
|
logger.error("Received process service call without a text")
|
||||||
|
return
|
||||||
|
|
||||||
|
text = service.data[ATTR_TEXT].lower()
|
||||||
|
|
||||||
|
match = REGEX_TURN_COMMAND.match(text)
|
||||||
|
|
||||||
|
if not match:
|
||||||
|
logger.error("Unable to process: %s", text)
|
||||||
|
return
|
||||||
|
|
||||||
|
name, command = match.groups()
|
||||||
|
|
||||||
|
entity_ids = [
|
||||||
|
state.entity_id for state in hass.states.all()
|
||||||
|
if state.attributes.get(ATTR_FRIENDLY_NAME, "").lower() == name]
|
||||||
|
|
||||||
|
if not entity_ids:
|
||||||
|
logger.error(
|
||||||
|
"Could not find entity id %s from text %s", name, text)
|
||||||
|
return
|
||||||
|
|
||||||
|
if command == 'on':
|
||||||
|
hass.services.call(
|
||||||
|
homeassistant.DOMAIN, SERVICE_TURN_ON, {
|
||||||
|
ATTR_ENTITY_ID: entity_ids,
|
||||||
|
}, blocking=True)
|
||||||
|
|
||||||
|
elif command == 'off':
|
||||||
|
hass.services.call(
|
||||||
|
homeassistant.DOMAIN, SERVICE_TURN_OFF, {
|
||||||
|
ATTR_ENTITY_ID: entity_ids,
|
||||||
|
}, blocking=True)
|
||||||
|
|
||||||
|
else:
|
||||||
|
logger.error(
|
||||||
|
'Got unsupported command %s from text %s', command, text)
|
||||||
|
|
||||||
|
hass.services.register(DOMAIN, SERVICE_PROCESS, process)
|
||||||
|
|
||||||
|
return True
|
@ -1,2 +1,2 @@
|
|||||||
""" DO NOT MODIFY. Auto-generated by build_frontend script """
|
""" DO NOT MODIFY. Auto-generated by build_frontend script """
|
||||||
VERSION = "1c265f0f07e6038c2cbb9b277e58b994"
|
VERSION = "832b49fd1e3ff3bc33e55812743c3a7d"
|
||||||
|
File diff suppressed because one or more lines are too long
@ -1,10 +1,5 @@
|
|||||||
<link rel="import" href="../bower_components/polymer/polymer.html">
|
<link rel="import" href="../bower_components/polymer/polymer.html">
|
||||||
|
|
||||||
<link rel="import" href="../bower_components/core-icon/core-icon.html">
|
|
||||||
<link rel="import" href="../bower_components/core-icons/social-icons.html">
|
|
||||||
<link rel="import" href="../bower_components/core-icons/image-icons.html">
|
|
||||||
<link rel="import" href="../bower_components/core-icons/hardware-icons.html">
|
|
||||||
|
|
||||||
<link rel="import" href="../resources/home-assistant-icons.html">
|
<link rel="import" href="../resources/home-assistant-icons.html">
|
||||||
|
|
||||||
<polymer-element name="domain-icon"
|
<polymer-element name="domain-icon"
|
||||||
|
@ -1 +1 @@
|
|||||||
Subproject commit 8ea3a9e858a8c39d4c3aa46b719362b33f4a358f
|
Subproject commit 9d0ce4e9b867ce7599969714a9c45f8d58a8463f
|
@ -1,4 +1,5 @@
|
|||||||
<link rel="import" href="../bower_components/polymer/polymer.html">
|
<link rel="import" href="../bower_components/polymer/polymer.html">
|
||||||
|
<link rel="import" href="../bower_components/core-icon/core-icon.html">
|
||||||
<link rel="import" href="../bower_components/core-style/core-style.html">
|
<link rel="import" href="../bower_components/core-style/core-style.html">
|
||||||
<link rel="import" href="../bower_components/paper-icon-button/paper-icon-button.html">
|
<link rel="import" href="../bower_components/paper-icon-button/paper-icon-button.html">
|
||||||
|
|
||||||
@ -10,14 +11,47 @@
|
|||||||
<template>
|
<template>
|
||||||
<core-style ref="ha-animations"></core-style>
|
<core-style ref="ha-animations"></core-style>
|
||||||
|
|
||||||
|
<style>
|
||||||
|
.listening {
|
||||||
|
border-radius: 2px;
|
||||||
|
box-shadow: rgba(0, 0, 0, 0.098) 0px 2px 4px, rgba(0, 0, 0, 0.098) 0px 0px 3px;
|
||||||
|
padding: 16px;
|
||||||
|
background-color: #FFF;
|
||||||
|
line-height: 2em;
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
|
||||||
|
@media all and (min-width: 1020px) {
|
||||||
|
.listening {
|
||||||
|
margin: 8px 8px 0 8px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.interimTranscript {
|
||||||
|
color: darkgrey;
|
||||||
|
}
|
||||||
|
|
||||||
|
.listening paper-spinner {
|
||||||
|
float: right;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
|
||||||
<partial-base narrow="{{narrow}}" togglePanel="{{togglePanel}}">
|
<partial-base narrow="{{narrow}}" togglePanel="{{togglePanel}}">
|
||||||
<span header-title>{{headerTitle}}</span>
|
<span header-title>{{headerTitle}}</span>
|
||||||
|
|
||||||
<span header-buttons>
|
<span header-buttons>
|
||||||
<paper-icon-button icon="refresh" class="{{isFetching && 'ha-spin'}}"
|
<paper-icon-button icon="refresh" class="{{isFetching && 'ha-spin'}}"
|
||||||
on-click="{{handleRefreshClick}}" hidden?="{{isStreaming}}"></paper-icon-button>
|
on-click="{{handleRefreshClick}}" hidden?="{{isStreaming}}"></paper-icon-button>
|
||||||
|
<paper-icon-button icon="{{isListening ? 'av:mic-off' : 'av:mic' }}" hidden?={{!canListen}}
|
||||||
|
on-click="{{handleListenClick}}"></paper-icon-button>
|
||||||
</span>
|
</span>
|
||||||
|
|
||||||
|
<div class='listening' hidden?="{{!isListening && !isTransmitting}}" on-click={{handleListenClick}}>
|
||||||
|
<core-icon icon="av:hearing"></core-icon> {{finalTranscript}}
|
||||||
|
<span class='interimTranscript'>{{interimTranscript}}</span>
|
||||||
|
<paper-spinner active?="{{isTransmitting}}"></paper-spinner>
|
||||||
|
</div>
|
||||||
|
|
||||||
<state-cards states="{{states}}">
|
<state-cards states="{{states}}">
|
||||||
<h3>Hi there!</h3>
|
<h3>Hi there!</h3>
|
||||||
<p>
|
<p>
|
||||||
@ -32,6 +66,7 @@
|
|||||||
<script>
|
<script>
|
||||||
var storeListenerMixIn = window.hass.storeListenerMixIn;
|
var storeListenerMixIn = window.hass.storeListenerMixIn;
|
||||||
var syncActions = window.hass.syncActions;
|
var syncActions = window.hass.syncActions;
|
||||||
|
var voiceActions = window.hass.voiceActions;
|
||||||
var stateStore = window.hass.stateStore;
|
var stateStore = window.hass.stateStore;
|
||||||
|
|
||||||
Polymer(Polymer.mixin({
|
Polymer(Polymer.mixin({
|
||||||
@ -40,6 +75,18 @@
|
|||||||
isFetching: false,
|
isFetching: false,
|
||||||
isStreaming: false,
|
isStreaming: false,
|
||||||
|
|
||||||
|
canListen: false,
|
||||||
|
voiceSupported: false,
|
||||||
|
hasConversationComponent: false,
|
||||||
|
isListening: false,
|
||||||
|
isTransmittingVoice: false,
|
||||||
|
interimTranscript: '',
|
||||||
|
finalTranscript: '',
|
||||||
|
|
||||||
|
ready: function() {
|
||||||
|
this.voiceSupported = voiceActions.isSupported();
|
||||||
|
},
|
||||||
|
|
||||||
attached: function() {
|
attached: function() {
|
||||||
this.listenToStores(true);
|
this.listenToStores(true);
|
||||||
},
|
},
|
||||||
@ -48,6 +95,11 @@
|
|||||||
this.stopListeningToStores();
|
this.stopListeningToStores();
|
||||||
},
|
},
|
||||||
|
|
||||||
|
componentStoreChanged: function(componentStore) {
|
||||||
|
this.canListen = this.voiceSupported &&
|
||||||
|
componentStore.isLoaded('conversation');
|
||||||
|
},
|
||||||
|
|
||||||
stateStoreChanged: function() {
|
stateStoreChanged: function() {
|
||||||
this.refreshStates();
|
this.refreshStates();
|
||||||
},
|
},
|
||||||
@ -60,6 +112,23 @@
|
|||||||
this.isStreaming = streamStore.isStreaming();
|
this.isStreaming = streamStore.isStreaming();
|
||||||
},
|
},
|
||||||
|
|
||||||
|
voiceStoreChanged: function(voiceStore) {
|
||||||
|
this.isListening = voiceStore.isListening();
|
||||||
|
this.isTransmitting = voiceStore.isTransmitting();
|
||||||
|
this.finalTranscript = voiceStore.getFinalTranscript();
|
||||||
|
|
||||||
|
var interimTranscript = voiceStore.getInterimTranscript();
|
||||||
|
|
||||||
|
var lengthFinal = this.finalTranscript.length;
|
||||||
|
|
||||||
|
// cut off finalTranscript part from beginning interimTranscript
|
||||||
|
if (interimTranscript.slice(0, lengthFinal) === this.finalTranscript) {
|
||||||
|
this.interimTranscript = interimTranscript.slice(lengthFinal);
|
||||||
|
} else {
|
||||||
|
this.interimTranscript = interimTranscript;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
filterChanged: function() {
|
filterChanged: function() {
|
||||||
this.refreshStates();
|
this.refreshStates();
|
||||||
|
|
||||||
@ -89,6 +158,14 @@
|
|||||||
handleRefreshClick: function() {
|
handleRefreshClick: function() {
|
||||||
syncActions.fetchAll();
|
syncActions.fetchAll();
|
||||||
},
|
},
|
||||||
|
|
||||||
|
handleListenClick: function() {
|
||||||
|
if (this.isListening) {
|
||||||
|
voiceActions.stop();
|
||||||
|
} else {
|
||||||
|
voiceActions.listen();
|
||||||
|
}
|
||||||
|
},
|
||||||
}, storeListenerMixIn));
|
}, storeListenerMixIn));
|
||||||
</script>
|
</script>
|
||||||
</polymer>
|
</polymer>
|
@ -1,6 +1,12 @@
|
|||||||
<link rel="import" href="../bower_components/core-icon/core-icon.html">
|
<link rel="import" href="../bower_components/core-icon/core-icon.html">
|
||||||
<link rel="import" href="../bower_components/core-iconset-svg/core-iconset-svg.html">
|
<link rel="import" href="../bower_components/core-iconset-svg/core-iconset-svg.html">
|
||||||
|
|
||||||
|
<link rel="import" href="../bower_components/core-icon/core-icon.html">
|
||||||
|
<link rel="import" href="../bower_components/core-icons/social-icons.html">
|
||||||
|
<link rel="import" href="../bower_components/core-icons/image-icons.html">
|
||||||
|
<link rel="import" href="../bower_components/core-icons/hardware-icons.html">
|
||||||
|
<link rel="import" href="../bower_components/core-icons/av-icons.html">
|
||||||
|
|
||||||
<core-iconset-svg id="homeassistant-100" iconSize="100">
|
<core-iconset-svg id="homeassistant-100" iconSize="100">
|
||||||
<svg><defs>
|
<svg><defs>
|
||||||
<g id="thermostat">
|
<g id="thermostat">
|
||||||
|
Loading…
x
Reference in New Issue
Block a user