Implemented support for assistant pause/resume conversation (closes #109)

This commit is contained in:
Fabio Manganiello 2020-01-19 16:21:29 +01:00
parent 89ae86492f
commit 6c797b0ad9
7 changed files with 122 additions and 20 deletions

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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