Implemented support for assistant pause/resume conversation (closes #109)
This commit is contained in:
parent
89ae86492f
commit
6c797b0ad9
7 changed files with 122 additions and 20 deletions
|
@ -0,0 +1,21 @@
|
||||||
|
import threading
|
||||||
|
|
||||||
|
from platypush.backend import Backend
|
||||||
|
|
||||||
|
|
||||||
|
class AssistantBackend(Backend):
|
||||||
|
def __init__(self, **kwargs):
|
||||||
|
super().__init__(**kwargs)
|
||||||
|
self._detection_paused = threading.Event()
|
||||||
|
|
||||||
|
def pause_detection(self):
|
||||||
|
self._detection_paused.set()
|
||||||
|
|
||||||
|
def resume_detection(self):
|
||||||
|
self._detection_paused.clear()
|
||||||
|
|
||||||
|
def is_detecting(self):
|
||||||
|
return not self._detection_paused.is_set()
|
||||||
|
|
||||||
|
|
||||||
|
# vim:sw=4:ts=4:et:
|
|
@ -7,7 +7,7 @@ import json
|
||||||
import os
|
import os
|
||||||
import time
|
import time
|
||||||
|
|
||||||
from platypush.backend import Backend
|
from platypush.backend.assistant import AssistantBackend
|
||||||
from platypush.message.event.assistant import \
|
from platypush.message.event.assistant import \
|
||||||
ConversationStartEvent, ConversationEndEvent, ConversationTimeoutEvent, \
|
ConversationStartEvent, ConversationEndEvent, ConversationTimeoutEvent, \
|
||||||
ResponseEvent, NoResponseEvent, SpeechRecognizedEvent, AlarmStartedEvent, \
|
ResponseEvent, NoResponseEvent, SpeechRecognizedEvent, AlarmStartedEvent, \
|
||||||
|
@ -15,7 +15,7 @@ from platypush.message.event.assistant import \
|
||||||
AlertEndEvent
|
AlertEndEvent
|
||||||
|
|
||||||
|
|
||||||
class AssistantGoogleBackend(Backend):
|
class AssistantGoogleBackend(AssistantBackend):
|
||||||
"""
|
"""
|
||||||
Google Assistant backend.
|
Google Assistant backend.
|
||||||
|
|
||||||
|
@ -158,12 +158,15 @@ class AssistantGoogleBackend(Backend):
|
||||||
with Assistant(self.credentials, self.device_model_id) as assistant:
|
with Assistant(self.credentials, self.device_model_id) as assistant:
|
||||||
self.assistant = assistant
|
self.assistant = assistant
|
||||||
for event in assistant.start():
|
for event in assistant.start():
|
||||||
|
if not self.is_detecting():
|
||||||
|
self.logger.info('Assistant event received but detection is currently paused')
|
||||||
|
continue
|
||||||
|
|
||||||
self._process_event(event)
|
self._process_event(event)
|
||||||
if self._has_error:
|
if self._has_error:
|
||||||
self.logger.info('Restarting the assistant after ' +
|
self.logger.info('Restarting the assistant after an unrecoverable error')
|
||||||
'an unrecoverable error')
|
|
||||||
time.sleep(5)
|
time.sleep(5)
|
||||||
continue
|
break
|
||||||
|
|
||||||
|
|
||||||
# vim:sw=4:ts=4:et:
|
# vim:sw=4:ts=4:et:
|
||||||
|
|
|
@ -6,12 +6,12 @@
|
||||||
import os
|
import os
|
||||||
import threading
|
import threading
|
||||||
|
|
||||||
from platypush.backend import Backend
|
from platypush.backend.assistant import AssistantBackend
|
||||||
from platypush.context import get_plugin
|
from platypush.context import get_plugin
|
||||||
from platypush.message.event.assistant import HotwordDetectedEvent
|
from platypush.message.event.assistant import HotwordDetectedEvent
|
||||||
|
|
||||||
|
|
||||||
class AssistantSnowboyBackend(Backend):
|
class AssistantSnowboyBackend(AssistantBackend):
|
||||||
"""
|
"""
|
||||||
Backend for detecting custom voice hotwords through Snowboy. The purpose of
|
Backend for detecting custom voice hotwords through Snowboy. The purpose of
|
||||||
this component is only to detect the hotword specified in your Snowboy voice
|
this component is only to detect the hotword specified in your Snowboy voice
|
||||||
|
@ -140,6 +140,10 @@ class AssistantSnowboyBackend(Backend):
|
||||||
snowboydecoder.play_audio_file(sound)
|
snowboydecoder.play_audio_file(sound)
|
||||||
|
|
||||||
def callback():
|
def callback():
|
||||||
|
if not self.is_detecting():
|
||||||
|
self.logger.info('Hotword detected but assistant response currently paused')
|
||||||
|
return
|
||||||
|
|
||||||
self.bus.post(HotwordDetectedEvent(hotword=hotword))
|
self.bus.post(HotwordDetectedEvent(hotword=hotword))
|
||||||
model = self.models[hotword]
|
model = self.models[hotword]
|
||||||
|
|
||||||
|
|
|
@ -2,6 +2,7 @@ from abc import ABC, abstractmethod
|
||||||
|
|
||||||
from platypush.plugins import Plugin
|
from platypush.plugins import Plugin
|
||||||
|
|
||||||
|
|
||||||
class AssistantPlugin(ABC, Plugin):
|
class AssistantPlugin(ABC, Plugin):
|
||||||
"""
|
"""
|
||||||
Base class for assistant plugins
|
Base class for assistant plugins
|
||||||
|
@ -9,10 +10,37 @@ class AssistantPlugin(ABC, Plugin):
|
||||||
|
|
||||||
@abstractmethod
|
@abstractmethod
|
||||||
def start_conversation(self, *args, language=None, **kwargs):
|
def start_conversation(self, *args, language=None, **kwargs):
|
||||||
|
"""
|
||||||
|
Start a conversation.
|
||||||
|
"""
|
||||||
raise NotImplementedError
|
raise NotImplementedError
|
||||||
|
|
||||||
@abstractmethod
|
@abstractmethod
|
||||||
def stop_conversation(self, *args, **kwargs):
|
def stop_conversation(self, *args, **kwargs):
|
||||||
|
"""
|
||||||
|
Stop a conversation.
|
||||||
|
"""
|
||||||
|
raise NotImplementedError
|
||||||
|
|
||||||
|
@abstractmethod
|
||||||
|
def pause_detection(self, *args, **kwargs):
|
||||||
|
"""
|
||||||
|
Put the assistant on pause. No new conversation events will be triggered.
|
||||||
|
"""
|
||||||
|
raise NotImplementedError
|
||||||
|
|
||||||
|
@abstractmethod
|
||||||
|
def resume_detection(self, *args, **kwargs):
|
||||||
|
"""
|
||||||
|
Resume the assistant hotword detection from a paused state.
|
||||||
|
"""
|
||||||
|
raise NotImplementedError
|
||||||
|
|
||||||
|
@abstractmethod
|
||||||
|
def is_detecting(self) -> bool:
|
||||||
|
"""
|
||||||
|
:return: True if the asistant is detecting, False otherwise.
|
||||||
|
"""
|
||||||
raise NotImplementedError
|
raise NotImplementedError
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -3,6 +3,7 @@
|
||||||
"""
|
"""
|
||||||
|
|
||||||
import os
|
import os
|
||||||
|
import threading
|
||||||
|
|
||||||
from platypush.context import get_bus
|
from platypush.context import get_bus
|
||||||
from platypush.plugins import action
|
from platypush.plugins import action
|
||||||
|
@ -70,6 +71,7 @@ class AssistantEchoPlugin(AssistantPlugin):
|
||||||
self.audio = Audio(device_name=audio_device)
|
self.audio = Audio(device_name=audio_device)
|
||||||
self.alexa = Alexa(avs_config_file, audio_player=audio_player)
|
self.alexa = Alexa(avs_config_file, audio_player=audio_player)
|
||||||
self._ready = False
|
self._ready = False
|
||||||
|
self._detection_paused = threading.Event()
|
||||||
|
|
||||||
self.alexa.state_listener.on_ready = self._on_ready()
|
self.alexa.state_listener.on_ready = self._on_ready()
|
||||||
self.alexa.state_listener.on_listening = self._on_listening()
|
self.alexa.state_listener.on_listening = self._on_listening()
|
||||||
|
@ -115,9 +117,6 @@ class AssistantEchoPlugin(AssistantPlugin):
|
||||||
|
|
||||||
@action
|
@action
|
||||||
def start_conversation(self, **kwargs):
|
def start_conversation(self, **kwargs):
|
||||||
"""
|
|
||||||
Programmatically start a conversation with the assistant
|
|
||||||
"""
|
|
||||||
if not self._ready:
|
if not self._ready:
|
||||||
raise RuntimeError('Echo assistant not ready')
|
raise RuntimeError('Echo assistant not ready')
|
||||||
|
|
||||||
|
@ -126,11 +125,20 @@ class AssistantEchoPlugin(AssistantPlugin):
|
||||||
|
|
||||||
@action
|
@action
|
||||||
def stop_conversation(self):
|
def stop_conversation(self):
|
||||||
"""
|
|
||||||
Programmatically stop a running conversation with the assistant
|
|
||||||
"""
|
|
||||||
self.audio.stop()
|
self.audio.stop()
|
||||||
self._on_finished()()
|
self._on_finished()()
|
||||||
|
|
||||||
|
@action
|
||||||
|
def pause_detection(self):
|
||||||
|
self._detection_paused.set()
|
||||||
|
|
||||||
|
@action
|
||||||
|
def resume_detection(self):
|
||||||
|
self._detection_paused.clear()
|
||||||
|
|
||||||
|
@action
|
||||||
|
def is_detecting(self) -> bool:
|
||||||
|
return not self._detection_paused.is_set()
|
||||||
|
|
||||||
|
|
||||||
# vim:sw=4:ts=4:et:
|
# vim:sw=4:ts=4:et:
|
||||||
|
|
|
@ -3,24 +3,30 @@
|
||||||
"""
|
"""
|
||||||
|
|
||||||
from platypush.context import get_backend
|
from platypush.context import get_backend
|
||||||
from platypush.plugins import Plugin, action
|
from platypush.plugins import action
|
||||||
|
from platypush.plugins.assistant import AssistantPlugin
|
||||||
|
|
||||||
class AssistantGooglePlugin(Plugin):
|
|
||||||
|
class AssistantGooglePlugin(AssistantPlugin):
|
||||||
"""
|
"""
|
||||||
Google assistant plugin.
|
Google assistant plugin.
|
||||||
It acts like a wrapper around the :mod:`platypush.backend.assistant.google`
|
It acts like a wrapper around the :mod:`platypush.backend.assistant.google`
|
||||||
backend to programmatically control the conversation status.
|
backend to programmatically control the conversation status.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
def __init__(self, *args, **kwargs):
|
def __init__(self, **kwargs):
|
||||||
super().__init__(*args, **kwargs)
|
super().__init__(**kwargs)
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def _get_assistant():
|
||||||
|
return get_backend('assistant.google')
|
||||||
|
|
||||||
@action
|
@action
|
||||||
def start_conversation(self):
|
def start_conversation(self):
|
||||||
"""
|
"""
|
||||||
Programmatically start a conversation with the assistant
|
Programmatically start a conversation with the assistant
|
||||||
"""
|
"""
|
||||||
assistant = get_backend('assistant.google')
|
assistant = self._get_assistant()
|
||||||
assistant.start_conversation()
|
assistant.start_conversation()
|
||||||
|
|
||||||
@action
|
@action
|
||||||
|
@ -28,8 +34,23 @@ class AssistantGooglePlugin(Plugin):
|
||||||
"""
|
"""
|
||||||
Programmatically stop a running conversation with the assistant
|
Programmatically stop a running conversation with the assistant
|
||||||
"""
|
"""
|
||||||
assistant = get_backend('assistant.google')
|
assistant = self._get_assistant()
|
||||||
assistant.stop_conversation()
|
assistant.stop_conversation()
|
||||||
|
|
||||||
|
@action
|
||||||
|
def pause_detection(self):
|
||||||
|
assistant = self._get_assistant()
|
||||||
|
assistant.pause_detection()
|
||||||
|
|
||||||
|
@action
|
||||||
|
def resume_detection(self):
|
||||||
|
assistant = self._get_assistant()
|
||||||
|
assistant.resume_detection()
|
||||||
|
|
||||||
|
@action
|
||||||
|
def is_detecting(self) -> bool:
|
||||||
|
assistant = self._get_assistant()
|
||||||
|
return assistant.is_detecting()
|
||||||
|
|
||||||
|
|
||||||
# vim:sw=4:ts=4:et:
|
# vim:sw=4:ts=4:et:
|
||||||
|
|
|
@ -4,6 +4,7 @@
|
||||||
|
|
||||||
import json
|
import json
|
||||||
import os
|
import os
|
||||||
|
import threading
|
||||||
|
|
||||||
from platypush.context import get_bus
|
from platypush.context import get_bus
|
||||||
from platypush.message.event.assistant import ConversationStartEvent, \
|
from platypush.message.event.assistant import ConversationStartEvent, \
|
||||||
|
@ -85,6 +86,7 @@ class AssistantGooglePushtotalkPlugin(AssistantPlugin):
|
||||||
self.play_response = play_response
|
self.play_response = play_response
|
||||||
self.assistant = None
|
self.assistant = None
|
||||||
self.interactions = []
|
self.interactions = []
|
||||||
|
self._detection_paused = threading.Event()
|
||||||
|
|
||||||
with open(self.device_config) as f:
|
with open(self.device_config) as f:
|
||||||
device = json.load(f)
|
device = json.load(f)
|
||||||
|
@ -219,6 +221,10 @@ class AssistantGooglePushtotalkPlugin(AssistantPlugin):
|
||||||
|
|
||||||
from platypush.plugins.assistant.google.lib import SampleAssistant
|
from platypush.plugins.assistant.google.lib import SampleAssistant
|
||||||
|
|
||||||
|
if not self.is_detecting():
|
||||||
|
self.logger.info('Conversation start event received but detection is currently paused')
|
||||||
|
return
|
||||||
|
|
||||||
if not language:
|
if not language:
|
||||||
language = self.language
|
language = self.language
|
||||||
|
|
||||||
|
@ -253,7 +259,6 @@ class AssistantGooglePushtotalkPlugin(AssistantPlugin):
|
||||||
|
|
||||||
@action
|
@action
|
||||||
def stop_conversation(self):
|
def stop_conversation(self):
|
||||||
""" Stop a conversation """
|
|
||||||
if self.assistant:
|
if self.assistant:
|
||||||
self.assistant.play_response = False
|
self.assistant.play_response = False
|
||||||
|
|
||||||
|
@ -274,5 +279,17 @@ class AssistantGooglePushtotalkPlugin(AssistantPlugin):
|
||||||
device_model_id=self.device_model_id,
|
device_model_id=self.device_model_id,
|
||||||
on=on))
|
on=on))
|
||||||
|
|
||||||
|
@action
|
||||||
|
def pause_detection(self):
|
||||||
|
self._detection_paused.set()
|
||||||
|
|
||||||
|
@action
|
||||||
|
def resume_detection(self):
|
||||||
|
self._detection_paused.clear()
|
||||||
|
|
||||||
|
@action
|
||||||
|
def is_detecting(self) -> bool:
|
||||||
|
return not self._detection_paused.is_set()
|
||||||
|
|
||||||
|
|
||||||
# vim:sw=4:ts=4:et:
|
# vim:sw=4:ts=4:et:
|
||||||
|
|
Loading…
Reference in a new issue