[assistant.google] Propagate plugin name as a string to events.
continuous-integration/drone/push Build is passing Details

This also makes it easier to programmatically stop conversations on
`SpeechRecognizedEvent` with a matched phrase.
This commit is contained in:
Fabio Manganiello 2023-12-21 00:28:27 +01:00
parent 199b42584f
commit 0de322fb95
Signed by: blacklight
GPG Key ID: D90FBA7F76362774
4 changed files with 69 additions and 35 deletions

View File

@ -255,6 +255,8 @@ class Event(Message):
else: else:
result.score = 0 result.score = 0
return result
def __str__(self): def __str__(self):
""" """
Overrides the str() operator and converts Overrides the str() operator and converts

View File

@ -1,15 +1,27 @@
import re import re
import sys import sys
from typing import Optional
from platypush.context import get_plugin
from platypush.message.event import Event from platypush.message.event import Event
class AssistantEvent(Event): class AssistantEvent(Event):
"""Base class for assistant events""" """Base class for assistant events"""
def __init__(self, *args, assistant=None, **kwargs): def __init__(self, *args, assistant: Optional[str] = None, **kwargs):
super().__init__(*args, **kwargs) """
self._assistant = assistant :param assistant: Name of the assistant plugin that triggered the event.
"""
super().__init__(*args, assistant=assistant, **kwargs)
@property
def _assistant(self):
return (
get_plugin(self.args.get('assistant'))
if self.args.get('assistant')
else None
)
class ConversationStartEvent(AssistantEvent): class ConversationStartEvent(AssistantEvent):
@ -26,10 +38,10 @@ class ConversationEndEvent(AssistantEvent):
Event triggered when a conversation ends Event triggered when a conversation ends
""" """
def __init__(self, *args, with_follow_on_turn=False, **kwargs): def __init__(self, *args, with_follow_on_turn: bool = False, **kwargs):
""" """
:param with_follow_on_turn: Set to true if the conversation expects a user follow-up, false otherwise :param with_follow_on_turn: Set to true if the conversation expects a
:type with_follow_on_turn: str user follow-up, false otherwise
""" """
super().__init__(*args, with_follow_on_turn=with_follow_on_turn, **kwargs) super().__init__(*args, with_follow_on_turn=with_follow_on_turn, **kwargs)
@ -49,10 +61,9 @@ class ResponseEvent(ConversationEndEvent):
Event triggered when a response is processed by the assistant Event triggered when a response is processed by the assistant
""" """
def __init__(self, response_text, *args, **kwargs): def __init__(self, *args, response_text: str, **kwargs):
""" """
:param response_text: Response text processed by the assistant :param response_text: Response text processed by the assistant
:type response_text: str
""" """
super().__init__(*args, response_text=response_text, **kwargs) super().__init__(*args, response_text=response_text, **kwargs)
@ -69,10 +80,9 @@ class SpeechRecognizedEvent(AssistantEvent):
Event triggered when a speech is recognized Event triggered when a speech is recognized
""" """
def __init__(self, phrase, *args, **kwargs): def __init__(self, *args, phrase: str, **kwargs):
""" """
:param phrase: Recognized user phrase :param phrase: Recognized user phrase
:type phrase: str
""" """
super().__init__(*args, phrase=phrase, **kwargs) super().__init__(*args, phrase=phrase, **kwargs)
@ -81,7 +91,7 @@ class SpeechRecognizedEvent(AssistantEvent):
def matches_condition(self, condition): def matches_condition(self, condition):
""" """
Overrides matches condition, and stops the conversation to prevent the Overrides matches condition, and stops the conversation to prevent the
default assistant response if the event matched some event hook condition default assistant response if the event matched some event hook condition.
""" """
result = super().matches_condition(condition) result = super().matches_condition(condition)
@ -167,20 +177,19 @@ class HotwordDetectedEvent(AssistantEvent):
Event triggered when a custom hotword is detected Event triggered when a custom hotword is detected
""" """
def __init__(self, *args, hotword=None, **kwargs): def __init__(self, *args, hotword: Optional[str] = None, **kwargs):
""" """
:param hotword: The detected user hotword :param hotword: The detected user hotword.
:type hotword: str
""" """
super().__init__(*args, hotword=hotword, **kwargs) super().__init__(*args, hotword=hotword, **kwargs)
class VolumeChangedEvent(AssistantEvent): class VolumeChangedEvent(AssistantEvent):
""" """
Event triggered when the volume of the assistant changes Event triggered when the volume of the assistant changes.
""" """
def __init__(self, volume, *args, **kwargs): def __init__(self, *args, volume: float, **kwargs):
super().__init__(*args, volume=volume, **kwargs) super().__init__(*args, volume=volume, **kwargs)

View File

@ -3,7 +3,7 @@ from dataclasses import asdict, dataclass
from enum import Enum from enum import Enum
import os import os
from threading import Event from threading import Event
from typing import Any, Collection, Dict, Optional from typing import Any, Collection, Dict, Optional, Type
from platypush.context import get_bus, get_plugin from platypush.context import get_bus, get_plugin
from platypush.entities.assistants import Assistant from platypush.entities.assistants import Assistant
@ -67,7 +67,8 @@ class AssistantPlugin(Plugin, AssistantEntityManager, ABC):
tts_plugin: Optional[str] = None, tts_plugin: Optional[str] = None,
tts_plugin_args: Optional[Dict[str, Any]] = None, tts_plugin_args: Optional[Dict[str, Any]] = None,
conversation_start_sound: Optional[str] = None, conversation_start_sound: Optional[str] = None,
**kwargs stop_conversation_on_speech_match: bool = False,
**kwargs,
): ):
""" """
:param tts_plugin: If set, the assistant will use this plugin (e.g. :param tts_plugin: If set, the assistant will use this plugin (e.g.
@ -81,10 +82,19 @@ class AssistantPlugin(Plugin, AssistantEntityManager, ABC):
audio file when it detects a speech. The sound file will be played audio file when it detects a speech. The sound file will be played
on the default audio output device. If not set, the assistant won't on the default audio output device. If not set, the assistant won't
play any sound when it detects a speech. play any sound when it detects a speech.
:param stop_conversation_on_speech_match: If set, the plugin will close the
conversation if the latest recognized speech matches a registered
:class:`platypush.message.event.assistant.SpeechRecognizedEvent` hook
with a phrase. This is usually set to ``True`` for
:class:`platypush.plugins.assistant.google.GoogleAssistantPlugin`,
as it overrides the default assistant response when a speech event is
actually handled on the application side.
""" """
super().__init__(*args, **kwargs) super().__init__(*args, **kwargs)
self.tts_plugin = tts_plugin self.tts_plugin = tts_plugin
self.tts_plugin_args = tts_plugin_args or {} self.tts_plugin_args = tts_plugin_args or {}
self.stop_conversation_on_speech_match = stop_conversation_on_speech_match
self._conversation_start_sound = None self._conversation_start_sound = None
if conversation_start_sound: if conversation_start_sound:
self._conversation_start_sound = os.path.abspath( self._conversation_start_sound = os.path.abspath(
@ -97,6 +107,7 @@ class AssistantPlugin(Plugin, AssistantEntityManager, ABC):
self._last_query: Optional[str] = None self._last_query: Optional[str] = None
self._last_response: Optional[str] = None self._last_response: Optional[str] = None
self._cur_alert_type: Optional[AlertType] = None self._cur_alert_type: Optional[AlertType] = None
self._plugin_name = get_plugin_name_by_class(type(self))
@property @property
def _state(self) -> AssistantState: def _state(self) -> AssistantState:
@ -189,35 +200,35 @@ class AssistantPlugin(Plugin, AssistantEntityManager, ABC):
audio.play(self._conversation_start_sound) audio.play(self._conversation_start_sound)
def _send_event(self, event: AssistantEvent): def _send_event(self, event_type: Type[AssistantEvent], **kwargs):
self.publish_entities([self]) self.publish_entities([self])
get_bus().post(event) get_bus().post(event_type(assistant=self._plugin_name, **kwargs))
def _on_conversation_start(self): def _on_conversation_start(self):
self._last_response = None self._last_response = None
self._last_query = None self._last_query = None
self._conversation_running.set() self._conversation_running.set()
self._send_event(ConversationStartEvent(assistant=self)) self._send_event(ConversationStartEvent)
self._play_conversation_start_sound() self._play_conversation_start_sound()
def _on_conversation_end(self): def _on_conversation_end(self):
self._conversation_running.clear() self._conversation_running.clear()
self._send_event(ConversationEndEvent(assistant=self)) self._send_event(ConversationEndEvent)
def _on_conversation_timeout(self): def _on_conversation_timeout(self):
self._last_response = None self._last_response = None
self._last_query = None self._last_query = None
self._conversation_running.clear() self._conversation_running.clear()
self._send_event(ConversationTimeoutEvent(assistant=self)) self._send_event(ConversationTimeoutEvent)
def _on_no_response(self): def _on_no_response(self):
self._last_response = None self._last_response = None
self._conversation_running.clear() self._conversation_running.clear()
self._send_event(NoResponseEvent(assistant=self)) self._send_event(NoResponseEvent)
def _on_reponse_rendered(self, text: Optional[str]): def _on_reponse_rendered(self, text: Optional[str]):
self._last_response = text self._last_response = text
self._send_event(ResponseEvent(assistant=self, response_text=text)) self._send_event(ResponseEvent, response_text=text)
tts = self._get_tts_plugin() tts = self._get_tts_plugin()
if tts and text: if tts and text:
@ -227,39 +238,39 @@ class AssistantPlugin(Plugin, AssistantEntityManager, ABC):
def _on_speech_recognized(self, phrase: Optional[str]): def _on_speech_recognized(self, phrase: Optional[str]):
phrase = (phrase or '').lower().strip() phrase = (phrase or '').lower().strip()
self._last_query = phrase self._last_query = phrase
self._send_event(SpeechRecognizedEvent(assistant=self, phrase=phrase)) self._send_event(SpeechRecognizedEvent, phrase=phrase)
def _on_alarm_start(self): def _on_alarm_start(self):
self._cur_alert_type = AlertType.ALARM self._cur_alert_type = AlertType.ALARM
self._send_event(AlarmStartedEvent(assistant=self)) self._send_event(AlarmStartedEvent)
def _on_alarm_end(self): def _on_alarm_end(self):
self._cur_alert_type = None self._cur_alert_type = None
self._send_event(AlarmEndEvent(assistant=self)) self._send_event(AlarmEndEvent)
def _on_timer_start(self): def _on_timer_start(self):
self._cur_alert_type = AlertType.TIMER self._cur_alert_type = AlertType.TIMER
self._send_event(TimerStartedEvent(assistant=self)) self._send_event(TimerStartedEvent)
def _on_timer_end(self): def _on_timer_end(self):
self._cur_alert_type = None self._cur_alert_type = None
self._send_event(TimerEndEvent(assistant=self)) self._send_event(TimerEndEvent)
def _on_alert_start(self): def _on_alert_start(self):
self._cur_alert_type = AlertType.ALERT self._cur_alert_type = AlertType.ALERT
self._send_event(AlertStartedEvent(assistant=self)) self._send_event(AlertStartedEvent)
def _on_alert_end(self): def _on_alert_end(self):
self._cur_alert_type = None self._cur_alert_type = None
self._send_event(AlertEndEvent(assistant=self)) self._send_event(AlertEndEvent)
def _on_mute(self): def _on_mute(self):
self._is_muted = True self._is_muted = True
self._send_event(MicMutedEvent(assistant=self)) self._send_event(MicMutedEvent)
def _on_unmute(self): def _on_unmute(self):
self._is_muted = False self._is_muted = False
self._send_event(MicUnmutedEvent(assistant=self)) self._send_event(MicUnmutedEvent)
def _on_mute_changed(self, value: bool): def _on_mute_changed(self, value: bool):
if value: if value:

View File

@ -76,6 +76,7 @@ class AssistantGooglePlugin(AssistantPlugin, RunnablePlugin):
self, self,
credentials_file: Optional[str] = None, credentials_file: Optional[str] = None,
device_model_id: str = 'Platypush', device_model_id: str = 'Platypush',
stop_conversation_on_speech_match: bool = True,
**kwargs, **kwargs,
): ):
""" """
@ -94,9 +95,20 @@ class AssistantGooglePlugin(AssistantPlugin, RunnablePlugin):
:param device_model_id: The device model ID that identifies the device :param device_model_id: The device model ID that identifies the device
where the assistant is running (default: Platypush). It can be a where the assistant is running (default: Platypush). It can be a
custom string. custom string.
:param stop_conversation_on_speech_match: If set, the plugin will close the
conversation if the latest recognized speech matches a registered
:class:`platypush.message.event.assistant.SpeechRecognizedEvent` hook
with a phrase. This is usually set to ``True`` for
:class:`platypush.plugins.assistant.google.GoogleAssistantPlugin`,
as it overrides the default assistant response when a speech event is
actually handled on the application side.
""" """
super().__init__(**kwargs) super().__init__(
stop_conversation_on_speech_match=stop_conversation_on_speech_match,
**kwargs,
)
self._credentials_file = credentials_file self._credentials_file = credentials_file
self.device_model_id = device_model_id self.device_model_id = device_model_id
self.credentials = None self.credentials = None