Merge pull request #57 from balloob/component-conversation

Add conversation component
This commit is contained in:
Paulus Schoutsen 2015-03-10 08:52:10 -07:00
commit c46e27928f
7 changed files with 168 additions and 15 deletions

View 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

View File

@ -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

View File

@ -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

View File

@ -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>

View File

@ -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">