[assistant.google] Propagate plugin name as a string to events.

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:
result.score = 0
return result
def __str__(self):
"""
Overrides the str() operator and converts

View file

@ -1,15 +1,27 @@
import re
import sys
from typing import Optional
from platypush.context import get_plugin
from platypush.message.event import Event
class AssistantEvent(Event):
"""Base class for assistant events"""
def __init__(self, *args, assistant=None, **kwargs):
super().__init__(*args, **kwargs)
self._assistant = assistant
def __init__(self, *args, assistant: Optional[str] = None, **kwargs):
"""
: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):
@ -26,10 +38,10 @@ class ConversationEndEvent(AssistantEvent):
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
:type with_follow_on_turn: str
:param with_follow_on_turn: Set to true if the conversation expects a
user follow-up, false otherwise
"""
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
"""
def __init__(self, response_text, *args, **kwargs):
def __init__(self, *args, response_text: str, **kwargs):
"""
:param response_text: Response text processed by the assistant
:type response_text: str
"""
super().__init__(*args, response_text=response_text, **kwargs)
@ -69,10 +80,9 @@ class SpeechRecognizedEvent(AssistantEvent):
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
:type phrase: str
"""
super().__init__(*args, phrase=phrase, **kwargs)
@ -81,7 +91,7 @@ class SpeechRecognizedEvent(AssistantEvent):
def matches_condition(self, condition):
"""
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)
@ -167,20 +177,19 @@ class HotwordDetectedEvent(AssistantEvent):
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
:type hotword: str
:param hotword: The detected user hotword.
"""
super().__init__(*args, hotword=hotword, **kwargs)
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)

View file

@ -3,7 +3,7 @@ from dataclasses import asdict, dataclass
from enum import Enum
import os
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.entities.assistants import Assistant
@ -67,7 +67,8 @@ class AssistantPlugin(Plugin, AssistantEntityManager, ABC):
tts_plugin: Optional[str] = None,
tts_plugin_args: Optional[Dict[str, Any]] = 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.
@ -81,10 +82,19 @@ class AssistantPlugin(Plugin, AssistantEntityManager, ABC):
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
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)
self.tts_plugin = tts_plugin
self.tts_plugin_args = tts_plugin_args or {}
self.stop_conversation_on_speech_match = stop_conversation_on_speech_match
self._conversation_start_sound = None
if conversation_start_sound:
self._conversation_start_sound = os.path.abspath(
@ -97,6 +107,7 @@ class AssistantPlugin(Plugin, AssistantEntityManager, ABC):
self._last_query: Optional[str] = None
self._last_response: Optional[str] = None
self._cur_alert_type: Optional[AlertType] = None
self._plugin_name = get_plugin_name_by_class(type(self))
@property
def _state(self) -> AssistantState:
@ -189,35 +200,35 @@ class AssistantPlugin(Plugin, AssistantEntityManager, ABC):
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])
get_bus().post(event)
get_bus().post(event_type(assistant=self._plugin_name, **kwargs))
def _on_conversation_start(self):
self._last_response = None
self._last_query = None
self._conversation_running.set()
self._send_event(ConversationStartEvent(assistant=self))
self._send_event(ConversationStartEvent)
self._play_conversation_start_sound()
def _on_conversation_end(self):
self._conversation_running.clear()
self._send_event(ConversationEndEvent(assistant=self))
self._send_event(ConversationEndEvent)
def _on_conversation_timeout(self):
self._last_response = None
self._last_query = None
self._conversation_running.clear()
self._send_event(ConversationTimeoutEvent(assistant=self))
self._send_event(ConversationTimeoutEvent)
def _on_no_response(self):
self._last_response = None
self._conversation_running.clear()
self._send_event(NoResponseEvent(assistant=self))
self._send_event(NoResponseEvent)
def _on_reponse_rendered(self, text: Optional[str]):
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()
if tts and text:
@ -227,39 +238,39 @@ class AssistantPlugin(Plugin, AssistantEntityManager, ABC):
def _on_speech_recognized(self, phrase: Optional[str]):
phrase = (phrase or '').lower().strip()
self._last_query = phrase
self._send_event(SpeechRecognizedEvent(assistant=self, phrase=phrase))
self._send_event(SpeechRecognizedEvent, phrase=phrase)
def _on_alarm_start(self):
self._cur_alert_type = AlertType.ALARM
self._send_event(AlarmStartedEvent(assistant=self))
self._send_event(AlarmStartedEvent)
def _on_alarm_end(self):
self._cur_alert_type = None
self._send_event(AlarmEndEvent(assistant=self))
self._send_event(AlarmEndEvent)
def _on_timer_start(self):
self._cur_alert_type = AlertType.TIMER
self._send_event(TimerStartedEvent(assistant=self))
self._send_event(TimerStartedEvent)
def _on_timer_end(self):
self._cur_alert_type = None
self._send_event(TimerEndEvent(assistant=self))
self._send_event(TimerEndEvent)
def _on_alert_start(self):
self._cur_alert_type = AlertType.ALERT
self._send_event(AlertStartedEvent(assistant=self))
self._send_event(AlertStartedEvent)
def _on_alert_end(self):
self._cur_alert_type = None
self._send_event(AlertEndEvent(assistant=self))
self._send_event(AlertEndEvent)
def _on_mute(self):
self._is_muted = True
self._send_event(MicMutedEvent(assistant=self))
self._send_event(MicMutedEvent)
def _on_unmute(self):
self._is_muted = False
self._send_event(MicUnmutedEvent(assistant=self))
self._send_event(MicUnmutedEvent)
def _on_mute_changed(self, value: bool):
if value:

View file

@ -76,6 +76,7 @@ class AssistantGooglePlugin(AssistantPlugin, RunnablePlugin):
self,
credentials_file: Optional[str] = None,
device_model_id: str = 'Platypush',
stop_conversation_on_speech_match: bool = True,
**kwargs,
):
"""
@ -94,9 +95,20 @@ class AssistantGooglePlugin(AssistantPlugin, RunnablePlugin):
:param device_model_id: The device model ID that identifies the device
where the assistant is running (default: Platypush). It can be a
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.device_model_id = device_model_id
self.credentials = None