From ea0e82f06eb22773100a0f317742b0fc06ea8f8e Mon Sep 17 00:00:00 2001 From: Mark Parker Date: Wed, 20 Sep 2023 18:29:41 +0200 Subject: [PATCH] add keyboard trigger + example --- stark_place/commands/general_manager.py | 2 +- stark_place/examples/keyboard_trigger.py | 56 ++++++++++++++++++++++++ stark_place/triggers/keyboard_key.py | 40 +++++++++++++++++ 3 files changed, 97 insertions(+), 1 deletion(-) create mode 100644 stark_place/examples/keyboard_trigger.py create mode 100644 stark_place/triggers/keyboard_key.py diff --git a/stark_place/commands/general_manager.py b/stark_place/commands/general_manager.py index a6cdde9..360f8a9 100644 --- a/stark_place/commands/general_manager.py +++ b/stark_place/commands/general_manager.py @@ -1,5 +1,5 @@ from stark import CommandsManager -general_manager = CommandsManager() +general_manager = CommandsManager('Stark-Place') # general_manager.extend(...) diff --git a/stark_place/examples/keyboard_trigger.py b/stark_place/examples/keyboard_trigger.py new file mode 100644 index 0000000..c07a4cf --- /dev/null +++ b/stark_place/examples/keyboard_trigger.py @@ -0,0 +1,56 @@ +import asyncer +from stark import CommandsContext, CommandsManager, Response +from stark.general.blockage_detector import BlockageDetector +from stark.interfaces.protocols import SpeechRecognizer, SpeechSynthesizer +from stark.interfaces.vosk import VoskSpeechRecognizer +from stark.interfaces.silero import SileroSpeechSynthesizer +from stark.voice_assistant import VoiceAssistant, Mode + +from stark_place.triggers import keyboard_key # import the trigger + + +VOSK_MODEL_URL = "YOUR_CHOSEN_VOSK_MODEL_URL" +SILERO_MODEL_URL = "YOUR_CHOSEN_SILERO_MODEL_URL" + +recognizer = VoskSpeechRecognizer(model_url=VOSK_MODEL_URL) +synthesizer = SileroSpeechSynthesizer(model_url=SILERO_MODEL_URL) + +manager = CommandsManager() + +@manager.new('hello') +async def hello_command() -> Response: + text = voice = 'Hello, world!' + return Response(text=text, voice=voice) + +async def run( + manager: CommandsManager, + speech_recognizer: SpeechRecognizer, + speech_synthesizer: SpeechSynthesizer +): + async with asyncer.create_task_group() as main_task_group: + context = CommandsContext( + task_group = main_task_group, + commands_manager = manager + ) + voice_assistant = VoiceAssistant( + speech_recognizer = speech_recognizer, + speech_synthesizer = speech_synthesizer, + commands_context = context + ) + speech_recognizer.delegate = voice_assistant + context.delegate = voice_assistant + + voice_assistant.mode = Mode.external() # stop listening after first response + + main_task_group.soonify(keyboard_key.start)(main_task_group.soonify(speech_recognizer.start_listening)) # add trigger to the main loop + # main_task_group.soonify(speech_recognizer.start_listening)() # don't start listening until the hotkey is pressed + main_task_group.soonify(context.handle_responses)() + + detector = BlockageDetector() + main_task_group.soonify(detector.monitor)() + +async def main(): + await run(manager, recognizer, synthesizer) + +if __name__ == '__main__': + asyncer.runnify(main)() # or anyio.run(main), same thing diff --git a/stark_place/triggers/keyboard_key.py b/stark_place/triggers/keyboard_key.py new file mode 100644 index 0000000..58b8817 --- /dev/null +++ b/stark_place/triggers/keyboard_key.py @@ -0,0 +1,40 @@ +from typing import Callable, Any +import os +from pynput.keyboard import Key, KeyCode, Listener, HotKey +import anyio +from asyncer import asyncify +from threading import Event + + +async def start(callback: Callable[[], Any | None]): + hotkey_event = Event() + + def on_release(key: Key): + # this function is called in a separate thread, so we need to use a thread-safe way to communicate with the main thread + nonlocal hotkey_event + if key == KeyCode(63): # macos globe button + hotkey_event.set() + + listener = Listener(on_release = on_release) + listener.start() # start listening in a separate thread + + while True: # this loop runs in the main thread + + await anyio.sleep(0.1) # release the thread for other tasks + + if hotkey_event.is_set(): + ''' + optional: play a sound to indicate that the hotkey was pressed + + play all sounds in macos using shell: sh`for s in /System/Library/Sounds/*; do echo "$s" && afplay "$s"; done` + for linux: check the `/usr/share/sounds/` directory and use `aplay` instead of `afplay` + + as an alternative, you can use the system-sounds pypi library to list and play sounds + pypi.org/project/system-sounds + or github.com/MarkParker5/system-sounds + + or use the a SpeechSynthesizer to say something like "Yes, sir?" + ''' + os.system('afplay /System/Library/Sounds/Blow.aiff &') # play the sound in the background (macos) + callback() + hotkey_event.clear()