Complete refactor
This commit is contained in:
@@ -130,10 +130,10 @@ You can also use `--mic-volume-multiplier X` to multiply all audio samples by `X
|
|||||||
|
|
||||||
Satellites can respond to events from the server by running commands:
|
Satellites can respond to events from the server by running commands:
|
||||||
|
|
||||||
* `--startup-command` - run when satellite starts
|
* `--startup-command` - run when satellite starts (no stdin)
|
||||||
* `--detect-command` - wake word detection has started, but not detected yet (no stdin)
|
* `--detect-command` - wake word detection has started, but not detected yet (no stdin)
|
||||||
* `--streaming-start-command` - audio has started streaming to server
|
* `--streaming-start-command` - audio has started streaming to server (no stdin)
|
||||||
* `--streaming-stop-command` - audio has stopped streaming to server
|
* `--streaming-stop-command` - audio has stopped streaming to server (no stdin)
|
||||||
* `--detection-command` - wake word is detected (wake word name on stdin)
|
* `--detection-command` - wake word is detected (wake word name on stdin)
|
||||||
* `--transcript-command` - speech-to-text transcript is returned (text on stdin)
|
* `--transcript-command` - speech-to-text transcript is returned (text on stdin)
|
||||||
* `--stt-start-command` - user started speaking (no stdin)
|
* `--stt-start-command` - user started speaking (no stdin)
|
||||||
|
|||||||
@@ -2,32 +2,17 @@
|
|||||||
import argparse
|
import argparse
|
||||||
import asyncio
|
import asyncio
|
||||||
import logging
|
import logging
|
||||||
import math
|
|
||||||
import shlex
|
import shlex
|
||||||
import sys
|
import sys
|
||||||
import time
|
import time
|
||||||
import uuid
|
import uuid
|
||||||
import wave
|
|
||||||
from enum import Enum
|
|
||||||
from functools import partial
|
from functools import partial
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
from typing import Callable, Final, Iterator, List, Optional, Union
|
from typing import List, Optional
|
||||||
|
|
||||||
from pyring_buffer import RingBuffer
|
|
||||||
from wyoming.asr import Transcribe, Transcript
|
|
||||||
from wyoming.audio import AudioChunk, AudioStart, AudioStop
|
|
||||||
from wyoming.client import AsyncClient
|
|
||||||
from wyoming.error import Error
|
|
||||||
from wyoming.event import Event
|
from wyoming.event import Event
|
||||||
from wyoming.info import Attribution, Describe, Info, Satellite
|
from wyoming.info import Attribution, Describe, Info, Satellite
|
||||||
from wyoming.mic import MicProcessAsyncClient
|
|
||||||
from wyoming.pipeline import PipelineStage, RunPipeline
|
|
||||||
from wyoming.satellite import RunSatellite
|
|
||||||
from wyoming.server import AsyncEventHandler, AsyncServer, AsyncTcpServer
|
from wyoming.server import AsyncEventHandler, AsyncServer, AsyncTcpServer
|
||||||
from wyoming.snd import SndProcessAsyncClient
|
|
||||||
from wyoming.tts import Synthesize
|
|
||||||
from wyoming.vad import VoiceStarted, VoiceStopped
|
|
||||||
from wyoming.wake import Detect, Detection, WakeProcessAsyncClient
|
|
||||||
|
|
||||||
from .satellite import (
|
from .satellite import (
|
||||||
AlwaysStreamingSatellite,
|
AlwaysStreamingSatellite,
|
||||||
@@ -43,13 +28,11 @@ from .settings import (
|
|||||||
VadSettings,
|
VadSettings,
|
||||||
WakeSettings,
|
WakeSettings,
|
||||||
)
|
)
|
||||||
from .utils import AudioBuffer, chunk_samples, multiply_volume, run_event_command
|
from .utils import run_event_command
|
||||||
|
|
||||||
_LOGGER = logging.getLogger()
|
_LOGGER = logging.getLogger()
|
||||||
_DIR = Path(__file__).parent
|
_DIR = Path(__file__).parent
|
||||||
|
|
||||||
# TODO: events service
|
|
||||||
|
|
||||||
|
|
||||||
async def main() -> None:
|
async def main() -> None:
|
||||||
"""Main entry point."""
|
"""Main entry point."""
|
||||||
@@ -396,59 +379,9 @@ class SatelliteEventHandler(AsyncEventHandler):
|
|||||||
self.client_id = str(time.monotonic_ns())
|
self.client_id = str(time.monotonic_ns())
|
||||||
self.satellite = satellite
|
self.satellite = satellite
|
||||||
|
|
||||||
# self.is_running = True
|
|
||||||
# self.state: Optional[Satellite] = None
|
|
||||||
# self.after_tts_state: Optional[Satellite] = None
|
|
||||||
# self.satellite_task: Optional[asyncio.Task] = None
|
|
||||||
|
|
||||||
# self.mic_volume_multiplier = self.cli_args.mic_volume_multiplier
|
|
||||||
# self.has_mic_audio_processing = needs_webrtc(self.cli_args) or (
|
|
||||||
# self.mic_volume_multiplier != 1.0
|
|
||||||
# )
|
|
||||||
# self.process_webrtc: Optional[Callable[[bytes], bytes]] = None
|
|
||||||
# if needs_webrtc(self.cli_args):
|
|
||||||
# self.process_webrtc = WebRtcAudio(
|
|
||||||
# self.cli_args.auto_gain, self.cli_args.noise_suppression
|
|
||||||
# )
|
|
||||||
|
|
||||||
# self.has_wake = bool(self.cli_args.wake_uri or self.cli_args.wake_command)
|
|
||||||
|
|
||||||
# self.has_snd = bool(self.cli_args.snd_uri or self.cli_args.snd_command)
|
|
||||||
# self.snd_client: Optional[AsyncClient] = None
|
|
||||||
# self.snd_volume_multiplier = self.cli_args.snd_volume_multiplier
|
|
||||||
# self.has_snd_audio_processing = self.snd_volume_multiplier != 1.0
|
|
||||||
|
|
||||||
# self.process_vad: Optional[Callable[[Optional[bytes]], bool]] = None
|
|
||||||
# self.vad_buffer: Optional[RingBuffer] = None
|
|
||||||
# if needs_silero(self.cli_args):
|
|
||||||
# self.process_vad = SileroVad(
|
|
||||||
# self.cli_args.vad_threshold, self.cli_args.vad_trigger_level
|
|
||||||
# )
|
|
||||||
# if self.cli_args.vad_buffer_seconds > 0:
|
|
||||||
# # Assume 16Khz, 16-bit mono samples
|
|
||||||
# vad_buffer_bytes = int(
|
|
||||||
# math.ceil(self.cli_args.vad_buffer_seconds * 16000 * 2)
|
|
||||||
# )
|
|
||||||
# self.vad_buffer = RingBuffer(vad_buffer_bytes)
|
|
||||||
|
|
||||||
# self.events_client: Optional[AsyncClient] = None
|
|
||||||
|
|
||||||
# self.server_state = server_state
|
|
||||||
# self.server_state.current_client_id = self.client_id
|
|
||||||
_LOGGER.debug("Client connected: %s", self.client_id)
|
_LOGGER.debug("Client connected: %s", self.client_id)
|
||||||
self.satellite.set_server(self.client_id, self.writer)
|
self.satellite.set_server(self.client_id, self.writer)
|
||||||
|
|
||||||
# self._stop_if_new_client_task = asyncio.create_task(self._stop_if_new_client())
|
|
||||||
|
|
||||||
# async def _stop_if_new_client(self) -> None:
|
|
||||||
# """Stop this event handler if a new client connects."""
|
|
||||||
# while self.is_running:
|
|
||||||
# if self.server_state.current_client_id != self.client_id:
|
|
||||||
# self.is_running = False
|
|
||||||
# _LOGGER.debug("Detected new client. Stopping old client.")
|
|
||||||
# else:
|
|
||||||
# await asyncio.sleep(1)
|
|
||||||
|
|
||||||
# -------------------------------------------------------------------------
|
# -------------------------------------------------------------------------
|
||||||
|
|
||||||
async def handle_event(self, event: Event) -> bool:
|
async def handle_event(self, event: Event) -> bool:
|
||||||
@@ -466,511 +399,6 @@ class SatelliteEventHandler(AsyncEventHandler):
|
|||||||
|
|
||||||
return True
|
return True
|
||||||
|
|
||||||
# async def handle_event(self, event: Event) -> bool:
|
|
||||||
# """Handle events from the server."""
|
|
||||||
# if Describe.is_type(event.type):
|
|
||||||
# await self.write_event(self.wyoming_info_event)
|
|
||||||
# _LOGGER.debug("Sent info to client: %s", self.client_id)
|
|
||||||
# return True
|
|
||||||
|
|
||||||
# if RunSatellite.is_type(event.type) and (self.satellite_task is None):
|
|
||||||
# if self.has_wake:
|
|
||||||
# # Local wake word detection
|
|
||||||
# self.state = SatelliteState.WAKE_START
|
|
||||||
# self.satellite_task = asyncio.create_task(self.run_satellite_wake())
|
|
||||||
# else:
|
|
||||||
# # Remote wake word detection
|
|
||||||
# self.state = SatelliteState.VAD_RESET
|
|
||||||
# self.satellite_task = asyncio.create_task(self.run_satellite())
|
|
||||||
# elif Transcript.is_type(event.type):
|
|
||||||
# if self.cli_args.done_wav:
|
|
||||||
# self.state = SatelliteState.PLAYING_AUDIO
|
|
||||||
# await self._play_wav(self.cli_args.done_wav)
|
|
||||||
|
|
||||||
# # STT transcript
|
|
||||||
# await self._forward_event(event)
|
|
||||||
# _LOGGER.debug("-> %s", event)
|
|
||||||
|
|
||||||
# if self.has_wake:
|
|
||||||
# # Ready for next wake word detection
|
|
||||||
# self.state = SatelliteState.WAKE_START
|
|
||||||
# else:
|
|
||||||
# self.state = SatelliteState.VAD_RESET
|
|
||||||
# elif self.has_snd and (
|
|
||||||
# AudioStart.is_type(event.type)
|
|
||||||
# or AudioChunk.is_type(event.type)
|
|
||||||
# or AudioStop.is_type(event.type)
|
|
||||||
# ):
|
|
||||||
# if AudioStart.is_type(event.type):
|
|
||||||
# # TTS start
|
|
||||||
# await self._ensure_snd_client()
|
|
||||||
# if self.snd_client is not None:
|
|
||||||
# await self.snd_client.connect()
|
|
||||||
|
|
||||||
# # State will be restored when audio ends
|
|
||||||
# self.after_tts_state = self.state
|
|
||||||
# self.state = SatelliteState.PLAYING_AUDIO
|
|
||||||
|
|
||||||
# await self._forward_event(event)
|
|
||||||
# _LOGGER.debug("-> %s", event)
|
|
||||||
# elif AudioChunk.is_type(event.type) and (self.snd_client is not None):
|
|
||||||
# # Forward to sound service
|
|
||||||
# event = self._process_snd_audio(event)
|
|
||||||
# await self.snd_client.write_event(event)
|
|
||||||
# elif AudioStop.is_type(event.type):
|
|
||||||
# # TTS stop
|
|
||||||
# await self._forward_event(event)
|
|
||||||
# _LOGGER.debug("-> %s", event)
|
|
||||||
|
|
||||||
# if self.snd_client is not None:
|
|
||||||
# await self.snd_client.disconnect()
|
|
||||||
# self.snd_client = None
|
|
||||||
|
|
||||||
# if self.after_tts_state is not None:
|
|
||||||
# # Restore state after playing audio
|
|
||||||
# self.state = self.after_tts_state
|
|
||||||
# self.after_tts_state = None
|
|
||||||
# elif Detection.is_type(event.type):
|
|
||||||
# if self.cli_args.awake_wav:
|
|
||||||
# last_state = self.state
|
|
||||||
# self.state = SatelliteState.PLAYING_AUDIO
|
|
||||||
# await self._play_wav(self.cli_args.awake_wav)
|
|
||||||
# self.state = last_state
|
|
||||||
|
|
||||||
# await self._forward_event(event)
|
|
||||||
# _LOGGER.debug("-> %s", event)
|
|
||||||
# elif (
|
|
||||||
# Detect.is_type(event.type)
|
|
||||||
# or Transcribe.is_type(event.type)
|
|
||||||
# or VoiceStarted.is_type(event.type)
|
|
||||||
# or VoiceStopped.is_type(event.type)
|
|
||||||
# or Synthesize.is_type(event.type)
|
|
||||||
# ):
|
|
||||||
# # Other client events:
|
|
||||||
# # - Detect for start of wake word detection
|
|
||||||
# # - Transcribe for when STT starts
|
|
||||||
# # - VoiceStarted/VoiceStopped for when user starts/stops speaking
|
|
||||||
# # - Synthesize for TTS text
|
|
||||||
# await self._forward_event(event)
|
|
||||||
# _LOGGER.debug("-> %s", event)
|
|
||||||
# elif Error.is_type(event.type):
|
|
||||||
# await self._forward_event(event)
|
|
||||||
# _LOGGER.warning(event)
|
|
||||||
# else:
|
|
||||||
# _LOGGER.debug("Unexpected event: type=%s, data=%s", event.type, event.data)
|
|
||||||
|
|
||||||
# return True
|
|
||||||
|
|
||||||
# async def disconnect(self) -> None:
|
|
||||||
# self.is_running = False
|
|
||||||
|
|
||||||
# # -------------------------------------------------------------------------
|
|
||||||
# # Tasks
|
|
||||||
# # -------------------------------------------------------------------------
|
|
||||||
|
|
||||||
# async def run_satellite(self) -> None:
|
|
||||||
# """Task to read mic input and do remote wake word detection."""
|
|
||||||
# vad_timeout_sec: Optional[float] = None
|
|
||||||
|
|
||||||
# try:
|
|
||||||
# mic_client = self._make_mic_client()
|
|
||||||
# async with mic_client:
|
|
||||||
# while self.is_running:
|
|
||||||
# mic_event = await mic_client.read_event()
|
|
||||||
# if mic_event is None:
|
|
||||||
# _LOGGER.warning("Microphone service disconnected")
|
|
||||||
# break
|
|
||||||
|
|
||||||
# if not AudioChunk.is_type(mic_event.type):
|
|
||||||
# continue
|
|
||||||
|
|
||||||
# mic_event = self._process_mic_audio(mic_event)
|
|
||||||
|
|
||||||
# if self.state == SatelliteState.VAD_RESET:
|
|
||||||
# # Reset vad state
|
|
||||||
# if self.process_vad is not None:
|
|
||||||
# self.process_vad(None)
|
|
||||||
|
|
||||||
# if self.vad_buffer is not None:
|
|
||||||
# self.vad_buffer.put(bytes(self.vad_buffer.maxlen))
|
|
||||||
|
|
||||||
# self.state = SatelliteState.VAD_WAIT
|
|
||||||
# if self.process_vad is not None:
|
|
||||||
# _LOGGER.info("Waiting for speech")
|
|
||||||
|
|
||||||
# if self.state == SatelliteState.VAD_WAIT:
|
|
||||||
# if self.process_vad is None:
|
|
||||||
# # No VAD
|
|
||||||
# self.state = SatelliteState.ASR
|
|
||||||
# _LOGGER.info("Streaming audio")
|
|
||||||
|
|
||||||
# # Start pipeline
|
|
||||||
# await self._run_pipeline(PipelineStage.WAKE)
|
|
||||||
# else:
|
|
||||||
# # Wait for speech
|
|
||||||
# chunk = AudioChunk.from_event(mic_event)
|
|
||||||
# if self.process_vad(chunk.audio):
|
|
||||||
# # Ready to stream
|
|
||||||
# self.state = SatelliteState.WAKE
|
|
||||||
|
|
||||||
# # Start pipeline
|
|
||||||
# await self._run_pipeline(PipelineStage.WAKE)
|
|
||||||
|
|
||||||
# if self.vad_buffer:
|
|
||||||
# # Send contents of VAD buffer first
|
|
||||||
# await self.write_event(
|
|
||||||
# AudioChunk(
|
|
||||||
# rate=chunk.rate,
|
|
||||||
# width=chunk.width,
|
|
||||||
# channels=chunk.channels,
|
|
||||||
# audio=self.vad_buffer.getvalue(),
|
|
||||||
# ).event()
|
|
||||||
# )
|
|
||||||
|
|
||||||
# if self.cli_args.vad_wake_word_timeout is not None:
|
|
||||||
# # Time in the future when timeout will occur
|
|
||||||
# vad_timeout_sec = (
|
|
||||||
# time.monotonic()
|
|
||||||
# + self.cli_args.vad_wake_word_timeout
|
|
||||||
# )
|
|
||||||
# else:
|
|
||||||
# # No timeout
|
|
||||||
# vad_timeout_sec = None
|
|
||||||
|
|
||||||
# _LOGGER.info("Streaming audio")
|
|
||||||
# elif self.vad_buffer is not None:
|
|
||||||
# # Save audio right before speech
|
|
||||||
# self.vad_buffer.put(chunk.audio)
|
|
||||||
|
|
||||||
# if self.state == SatelliteState.WAKE:
|
|
||||||
# if (vad_timeout_sec is not None) and (
|
|
||||||
# time.monotonic() >= vad_timeout_sec
|
|
||||||
# ):
|
|
||||||
# # Wake word was not detected within timeout
|
|
||||||
# self.state = SatelliteState.VAD_RESET
|
|
||||||
|
|
||||||
# # Cancel current pipeline with audio stop
|
|
||||||
# chunk = AudioChunk.from_event(mic_event)
|
|
||||||
# await self.write_event(AudioStop().event())
|
|
||||||
# continue
|
|
||||||
|
|
||||||
# # Remote wake word detection
|
|
||||||
# await self.write_event(mic_event)
|
|
||||||
# elif self.state == SatelliteState.ASR:
|
|
||||||
# # Forward all audio to server
|
|
||||||
# await self.write_event(mic_event)
|
|
||||||
|
|
||||||
# except ConnectionResetError:
|
|
||||||
# _LOGGER.info("Server disconnected")
|
|
||||||
# except Exception:
|
|
||||||
# _LOGGER.exception("Unexpected error in run_satellite")
|
|
||||||
|
|
||||||
# async def run_satellite_wake(self) -> None:
|
|
||||||
# """Task to read mic input and do local wake word detection."""
|
|
||||||
# try:
|
|
||||||
# mic_client = self._make_mic_client()
|
|
||||||
# wake_client = await self._connect_to_wake()
|
|
||||||
|
|
||||||
# async with mic_client, wake_client:
|
|
||||||
# # Read events in parallel
|
|
||||||
# mic_task = asyncio.create_task(mic_client.read_event())
|
|
||||||
# wake_task = asyncio.create_task(wake_client.read_event())
|
|
||||||
# pending = {mic_task, wake_task}
|
|
||||||
|
|
||||||
# while self.is_running:
|
|
||||||
# if self.state == SatelliteState.WAKE_START:
|
|
||||||
# # Send detect event
|
|
||||||
# _LOGGER.info("Detecting wake word")
|
|
||||||
|
|
||||||
# if self.cli_args.wake_word_name:
|
|
||||||
# # Request specific wake word(s)
|
|
||||||
# detect_event = Detect(
|
|
||||||
# names=self.cli_args.wake_word_name
|
|
||||||
# ).event()
|
|
||||||
# else:
|
|
||||||
# detect_event = Detect().event()
|
|
||||||
|
|
||||||
# _LOGGER.debug("<- %s", detect_event)
|
|
||||||
# await wake_client.write_event(detect_event)
|
|
||||||
# await self._forward_event(detect_event)
|
|
||||||
|
|
||||||
# self.state = SatelliteState.WAKE
|
|
||||||
|
|
||||||
# # Wait for mic/wake events
|
|
||||||
# done, pending = await asyncio.wait(
|
|
||||||
# pending, return_when=asyncio.FIRST_COMPLETED
|
|
||||||
# )
|
|
||||||
|
|
||||||
# if mic_task in done:
|
|
||||||
# mic_event = mic_task.result()
|
|
||||||
# if mic_event is None:
|
|
||||||
# _LOGGER.warning("Microphone service disconnected")
|
|
||||||
# break
|
|
||||||
|
|
||||||
# if AudioChunk.is_type(mic_event.type):
|
|
||||||
# mic_event = self._process_mic_audio(mic_event)
|
|
||||||
|
|
||||||
# # Forward to wake service
|
|
||||||
# await wake_client.write_event(mic_event)
|
|
||||||
|
|
||||||
# if self.state == SatelliteState.ASR:
|
|
||||||
# # Forward to server
|
|
||||||
# await self.write_event(mic_event)
|
|
||||||
|
|
||||||
# # Next audio chunk
|
|
||||||
# mic_task = asyncio.create_task(mic_client.read_event())
|
|
||||||
# pending.add(mic_task)
|
|
||||||
# elif wake_task in done:
|
|
||||||
# wake_event = wake_task.result()
|
|
||||||
# if wake_event is None:
|
|
||||||
# _LOGGER.warning("Wake word service disconnected")
|
|
||||||
# break
|
|
||||||
|
|
||||||
# if Detection.is_type(wake_event.type) and (
|
|
||||||
# self.state == SatelliteState.WAKE
|
|
||||||
# ):
|
|
||||||
# _LOGGER.debug("Wake word detected: %s", wake_event)
|
|
||||||
|
|
||||||
# if self.cli_args.awake_wav:
|
|
||||||
# self.state = SatelliteState.PLAYING_AUDIO
|
|
||||||
# await self._play_wav(self.cli_args.awake_wav)
|
|
||||||
|
|
||||||
# self.state = SatelliteState.ASR
|
|
||||||
|
|
||||||
# # Start pipeline
|
|
||||||
# await self._run_pipeline(PipelineStage.ASR)
|
|
||||||
|
|
||||||
# # Forward to server
|
|
||||||
# await self.write_event(wake_event)
|
|
||||||
|
|
||||||
# # Forward to client
|
|
||||||
# await self._forward_event(wake_event)
|
|
||||||
|
|
||||||
# _LOGGER.info("Streaming audio")
|
|
||||||
|
|
||||||
# # Next wake event
|
|
||||||
# wake_task = asyncio.create_task(wake_client.read_event())
|
|
||||||
# pending.add(wake_task)
|
|
||||||
# except ConnectionResetError:
|
|
||||||
# _LOGGER.info("Server disconnected")
|
|
||||||
# except Exception:
|
|
||||||
# _LOGGER.exception("Unexpected error in run_satellite")
|
|
||||||
|
|
||||||
# async def _connect_to_wake(self) -> AsyncClient:
|
|
||||||
# assert self.cli_args.wake_uri or self.cli_args.wake_command
|
|
||||||
|
|
||||||
# if self.cli_args.wake_command:
|
|
||||||
# _LOGGER.debug("Running %s", self.cli_args.wake_command)
|
|
||||||
# program, *program_args = shlex.split(self.cli_args.wake_command)
|
|
||||||
# return WakeProcessAsyncClient(
|
|
||||||
# rate=self.cli_args.wake_command_rate,
|
|
||||||
# width=self.cli_args.wake_command_width,
|
|
||||||
# channels=self.cli_args.wake_command_channels,
|
|
||||||
# program=program,
|
|
||||||
# program_args=program_args,
|
|
||||||
# )
|
|
||||||
|
|
||||||
# while True:
|
|
||||||
# try:
|
|
||||||
# _LOGGER.debug("Connecting to %s", self.cli_args.wake_uri)
|
|
||||||
# return AsyncClient.from_uri(self.cli_args.wake_uri)
|
|
||||||
# except (ConnectionError, OSError):
|
|
||||||
# _LOGGER.exception(
|
|
||||||
# "Unexpected error connecting to wake service. "
|
|
||||||
# "Reconnecting in %s second(s)",
|
|
||||||
# _WAKE_RECONNECT_SECONDS,
|
|
||||||
# )
|
|
||||||
# await asyncio.sleep(_WAKE_RECONNECT_SECONDS)
|
|
||||||
|
|
||||||
# # -------------------------------------------------------------------------
|
|
||||||
|
|
||||||
# def _make_mic_client(self) -> AsyncClient:
|
|
||||||
# """Create mic client."""
|
|
||||||
# if self.cli_args.mic_command:
|
|
||||||
# _LOGGER.debug("Running %s", self.cli_args.mic_command)
|
|
||||||
# program, *program_args = shlex.split(self.cli_args.mic_command)
|
|
||||||
# return MicProcessAsyncClient(
|
|
||||||
# rate=self.cli_args.mic_command_rate,
|
|
||||||
# width=self.cli_args.mic_command_width,
|
|
||||||
# channels=self.cli_args.mic_command_channels,
|
|
||||||
# samples_per_chunk=self.cli_args.mic_command_samples_per_chunk,
|
|
||||||
# program=program,
|
|
||||||
# program_args=program_args,
|
|
||||||
# )
|
|
||||||
|
|
||||||
# assert self.cli_args.mic_uri
|
|
||||||
# _LOGGER.debug("Connecting to %s", self.cli_args.mic_uri)
|
|
||||||
# return AsyncClient.from_uri(self.cli_args.mic_uri)
|
|
||||||
|
|
||||||
# async def _ensure_snd_client(self) -> None:
|
|
||||||
# """Create snd client if necessary."""
|
|
||||||
# if (not self.has_snd) or (self.snd_client is not None):
|
|
||||||
# return
|
|
||||||
|
|
||||||
# if self.cli_args.snd_command:
|
|
||||||
# _LOGGER.debug("Running %s", self.cli_args.snd_command)
|
|
||||||
# program, *program_args = shlex.split(self.cli_args.snd_command)
|
|
||||||
# self.snd_client = SndProcessAsyncClient(
|
|
||||||
# rate=self.cli_args.snd_command_rate,
|
|
||||||
# width=self.cli_args.snd_command_width,
|
|
||||||
# channels=self.cli_args.snd_command_channels,
|
|
||||||
# program=program,
|
|
||||||
# program_args=program_args,
|
|
||||||
# )
|
|
||||||
# else:
|
|
||||||
# _LOGGER.debug("Connecting to %s", self.cli_args.snd_uri)
|
|
||||||
# self.snd_client = AsyncClient.from_uri(self.cli_args.snd_uri)
|
|
||||||
|
|
||||||
# async def _run_pipeline(self, start_stage: PipelineStage) -> None:
|
|
||||||
# """Tell server to start running the pipeline."""
|
|
||||||
# await self.write_event(
|
|
||||||
# RunPipeline(
|
|
||||||
# start_stage=start_stage,
|
|
||||||
# end_stage=PipelineStage.TTS if self.has_snd else PipelineStage.HANDLE,
|
|
||||||
# ).event()
|
|
||||||
# )
|
|
||||||
|
|
||||||
# if self.cli_args.streaming_start_command:
|
|
||||||
# await run_event_command(self.cli_args.streaming_start_command)
|
|
||||||
|
|
||||||
# def _process_mic_audio(self, audio_event: Event) -> Event:
|
|
||||||
# """Perform microphone audio processing, if necessary."""
|
|
||||||
# if not self.has_mic_audio_processing:
|
|
||||||
# # No processing needed
|
|
||||||
# return audio_event
|
|
||||||
|
|
||||||
# chunk = AudioChunk.from_event(audio_event)
|
|
||||||
# audio_bytes = chunk.audio
|
|
||||||
|
|
||||||
# if self.process_webrtc is not None:
|
|
||||||
# audio_bytes = self.process_webrtc(audio_bytes)
|
|
||||||
|
|
||||||
# if self.mic_volume_multiplier != 1.0:
|
|
||||||
# audio_bytes = multiply_volume(audio_bytes, self.mic_volume_multiplier)
|
|
||||||
|
|
||||||
# return AudioChunk(
|
|
||||||
# rate=chunk.rate,
|
|
||||||
# width=chunk.width,
|
|
||||||
# channels=chunk.channels,
|
|
||||||
# audio=audio_bytes,
|
|
||||||
# ).event()
|
|
||||||
|
|
||||||
# def _process_snd_audio(self, audio_event: Event) -> Event:
|
|
||||||
# """Perform output audio processing, if necessary."""
|
|
||||||
# if not self.has_snd_audio_processing:
|
|
||||||
# # No processing needed
|
|
||||||
# return audio_event
|
|
||||||
|
|
||||||
# chunk = AudioChunk.from_event(audio_event)
|
|
||||||
# audio_bytes = chunk.audio
|
|
||||||
|
|
||||||
# if self.snd_volume_multiplier != 1.0:
|
|
||||||
# audio_bytes = multiply_volume(audio_bytes, self.snd_volume_multiplier)
|
|
||||||
|
|
||||||
# return AudioChunk(
|
|
||||||
# rate=chunk.rate,
|
|
||||||
# width=chunk.width,
|
|
||||||
# channels=chunk.channels,
|
|
||||||
# audio=audio_bytes,
|
|
||||||
# ).event()
|
|
||||||
|
|
||||||
# async def _forward_event(self, event: Event) -> None:
|
|
||||||
# """Forward a Wyoming event to a client and run event commands."""
|
|
||||||
# if self.cli_args.detect_command and Detect.is_type(event.type):
|
|
||||||
# # Wake word detection started
|
|
||||||
# await run_event_command(self.cli_args.detect_command)
|
|
||||||
# elif self.cli_args.detection_command and Detection.is_type(event.type):
|
|
||||||
# # Wake word is detected
|
|
||||||
# detection = Detection.from_event(event)
|
|
||||||
# await run_event_command(self.cli_args.detection_command, detection.name)
|
|
||||||
# elif self.cli_args.transcript_command and Transcript.is_type(event.type):
|
|
||||||
# # STT text is available
|
|
||||||
# transcript = Transcript.from_event(event)
|
|
||||||
# await run_event_command(self.cli_args.transcript_command, transcript.text)
|
|
||||||
# elif self.cli_args.stt_start_command and VoiceStarted.is_type(event.type):
|
|
||||||
# # User starts speaking
|
|
||||||
# await run_event_command(self.cli_args.stt_start_command)
|
|
||||||
# elif self.cli_args.stt_stop_command and VoiceStopped.is_type(event.type):
|
|
||||||
# # User stops speaking
|
|
||||||
# await run_event_command(self.cli_args.stt_stop_command)
|
|
||||||
# elif self.cli_args.synthesize_command and Synthesize.is_type(event.type):
|
|
||||||
# # TTS text is available
|
|
||||||
# synthesize = Synthesize.from_event(event)
|
|
||||||
# await run_event_command(self.cli_args.synthesize_command, synthesize.text)
|
|
||||||
# elif self.cli_args.tts_start_command and AudioStart.is_type(event.type):
|
|
||||||
# # TTS audio start
|
|
||||||
# await run_event_command(self.cli_args.tts_start_command)
|
|
||||||
# elif self.cli_args.tts_stop_command and AudioStop.is_type(event.type):
|
|
||||||
# # TTS audio stop
|
|
||||||
# await run_event_command(self.cli_args.tts_stop_command)
|
|
||||||
# elif self.cli_args.error_command and Error.is_type(event.type):
|
|
||||||
# # Error occurred
|
|
||||||
# error = Error.from_event(event)
|
|
||||||
# await run_event_command(self.cli_args.error_command, error.text)
|
|
||||||
|
|
||||||
# if not self.cli_args.event_uri:
|
|
||||||
# # No external service
|
|
||||||
# return
|
|
||||||
|
|
||||||
# if self.events_client is None:
|
|
||||||
# self.events_client = AsyncClient.from_uri(self.cli_args.event_uri)
|
|
||||||
# _LOGGER.debug("Connecting to %s", self.cli_args.event_uri)
|
|
||||||
# await self.events_client.connect()
|
|
||||||
|
|
||||||
# assert self.events_client is not None
|
|
||||||
# await self.events_client.write_event(event)
|
|
||||||
|
|
||||||
# async def _play_wav(self, wav_path: Union[str, Path]) -> None:
|
|
||||||
# """Send WAV file to snd client as audio chunks."""
|
|
||||||
# await self._ensure_snd_client()
|
|
||||||
|
|
||||||
# if self.snd_client is None:
|
|
||||||
# _LOGGER.debug("Cannot play WAV, no snd service")
|
|
||||||
# return
|
|
||||||
|
|
||||||
# try:
|
|
||||||
# async with self.snd_client:
|
|
||||||
# for event in self._wav_to_events(wav_path):
|
|
||||||
# await self.snd_client.write_event(event)
|
|
||||||
# except Exception:
|
|
||||||
# _LOGGER.exception("Unexpected error while playing WAV: %s", wav_path)
|
|
||||||
# finally:
|
|
||||||
# self.snd_client = None
|
|
||||||
|
|
||||||
# def _wav_to_events(
|
|
||||||
# self, wav_path: Union[str, Path], samples_per_chunk: int = 1024
|
|
||||||
# ) -> Iterator[Event]:
|
|
||||||
# """Load WAV audio for playback on an event (wake/done)."""
|
|
||||||
# with wave.open(str(wav_path), "rb") as wav_file:
|
|
||||||
# rate = wav_file.getframerate()
|
|
||||||
# width = wav_file.getsampwidth()
|
|
||||||
# channels = wav_file.getnchannels()
|
|
||||||
|
|
||||||
# timestamp = 0
|
|
||||||
# yield AudioStart(
|
|
||||||
# rate=rate, width=width, channels=channels, timestamp=timestamp
|
|
||||||
# ).event()
|
|
||||||
|
|
||||||
# audio_bytes = wav_file.readframes(samples_per_chunk)
|
|
||||||
# while audio_bytes:
|
|
||||||
# if self.snd_volume_multiplier != 1.0:
|
|
||||||
# audio_bytes = multiply_volume(
|
|
||||||
# audio_bytes, self.snd_volume_multiplier
|
|
||||||
# )
|
|
||||||
|
|
||||||
# chunk = AudioChunk(
|
|
||||||
# rate=rate,
|
|
||||||
# width=width,
|
|
||||||
# channels=channels,
|
|
||||||
# audio=audio_bytes,
|
|
||||||
# timestamp=timestamp,
|
|
||||||
# )
|
|
||||||
# yield chunk.event()
|
|
||||||
# timestamp += int(chunk.seconds * 1000)
|
|
||||||
# audio_bytes = wav_file.readframes(samples_per_chunk)
|
|
||||||
|
|
||||||
# yield AudioStop(timestamp=timestamp).event()
|
|
||||||
|
|
||||||
|
|
||||||
# -----------------------------------------------------------------------------
|
# -----------------------------------------------------------------------------
|
||||||
|
|
||||||
|
|||||||
+155
-52
@@ -3,20 +3,22 @@ import asyncio
|
|||||||
import logging
|
import logging
|
||||||
import math
|
import math
|
||||||
import time
|
import time
|
||||||
from dataclasses import dataclass
|
|
||||||
from enum import Enum, auto
|
from enum import Enum, auto
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
from typing import Callable, List, Optional, Set, Union
|
from typing import Callable, Optional, Set, Union
|
||||||
|
|
||||||
from pyring_buffer import RingBuffer
|
from pyring_buffer import RingBuffer
|
||||||
from wyoming.asr import Transcript
|
from wyoming.asr import Transcript
|
||||||
from wyoming.audio import AudioChunk, AudioStart, AudioStop
|
from wyoming.audio import AudioChunk, AudioStart, AudioStop
|
||||||
from wyoming.client import AsyncClient, AsyncTcpClient
|
from wyoming.client import AsyncClient
|
||||||
from wyoming.event import Event, async_read_event, async_write_event
|
from wyoming.error import Error
|
||||||
|
from wyoming.event import Event, async_write_event
|
||||||
from wyoming.mic import MicProcessAsyncClient
|
from wyoming.mic import MicProcessAsyncClient
|
||||||
from wyoming.pipeline import PipelineStage, RunPipeline
|
from wyoming.pipeline import PipelineStage, RunPipeline
|
||||||
from wyoming.satellite import RunSatellite
|
from wyoming.satellite import RunSatellite
|
||||||
from wyoming.snd import SndProcessAsyncClient
|
from wyoming.snd import SndProcessAsyncClient
|
||||||
|
from wyoming.tts import Synthesize
|
||||||
|
from wyoming.vad import VoiceStarted, VoiceStopped
|
||||||
from wyoming.wake import Detect, Detection, WakeProcessAsyncClient
|
from wyoming.wake import Detect, Detection, WakeProcessAsyncClient
|
||||||
|
|
||||||
from .settings import SatelliteSettings
|
from .settings import SatelliteSettings
|
||||||
@@ -53,6 +55,8 @@ class SatelliteBase:
|
|||||||
self._snd_queue: "Optional[asyncio.Queue[Event]]" = None
|
self._snd_queue: "Optional[asyncio.Queue[Event]]" = None
|
||||||
self._wake_task: Optional[asyncio.Task] = None
|
self._wake_task: Optional[asyncio.Task] = None
|
||||||
self._wake_queue: "Optional[asyncio.Queue[Event]]" = None
|
self._wake_queue: "Optional[asyncio.Queue[Event]]" = None
|
||||||
|
self._event_task: Optional[asyncio.Task] = None
|
||||||
|
self._event_queue: "Optional[asyncio.Queue[Event]]" = None
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def is_running(self) -> bool:
|
def is_running(self) -> bool:
|
||||||
@@ -121,31 +125,45 @@ class SatelliteBase:
|
|||||||
|
|
||||||
async def event_from_server(self, event: Event) -> None:
|
async def event_from_server(self, event: Event) -> None:
|
||||||
"""Called when an event is received from the server."""
|
"""Called when an event is received from the server."""
|
||||||
if RunSatellite.is_type(event.type):
|
if AudioChunk.is_type(event.type):
|
||||||
if self.settings.wake.enabled:
|
# TTS audio
|
||||||
# Local wake word detection
|
|
||||||
start_stage = PipelineStage.ASR
|
|
||||||
else:
|
|
||||||
# Remote wake word detection
|
|
||||||
start_stage = PipelineStage.WAKE
|
|
||||||
|
|
||||||
if self.settings.snd.enabled:
|
|
||||||
# Play TTS response
|
|
||||||
end_stage = PipelineStage.TTS
|
|
||||||
else:
|
|
||||||
# No audio output
|
|
||||||
end_stage = PipelineStage.INTENT
|
|
||||||
|
|
||||||
await self.event_to_server(
|
|
||||||
RunPipeline(start_stage=start_stage, end_stage=end_stage).event()
|
|
||||||
)
|
|
||||||
elif (
|
|
||||||
AudioChunk.is_type(event.type)
|
|
||||||
or AudioStart.is_type(event.type)
|
|
||||||
or AudioStop.is_type(event.type)
|
|
||||||
):
|
|
||||||
# Forward to sound service
|
|
||||||
await self.event_to_snd(event)
|
await self.event_to_snd(event)
|
||||||
|
elif AudioStart.is_type(event.type):
|
||||||
|
# TTS started
|
||||||
|
await self.event_to_snd(event)
|
||||||
|
await self.trigger_tts_start()
|
||||||
|
elif AudioStop.is_type(event.type):
|
||||||
|
# TTS stopped
|
||||||
|
await self.event_to_snd(event)
|
||||||
|
await self.trigger_tts_stop()
|
||||||
|
elif Detect.is_type(event.type):
|
||||||
|
# Wake word detection started
|
||||||
|
await self.trigger_detect()
|
||||||
|
elif Detection.is_type(event.type):
|
||||||
|
# Wake word detected
|
||||||
|
_LOGGER.debug("Wake word detected")
|
||||||
|
await self.trigger_detection(Detection.from_event(event))
|
||||||
|
elif VoiceStarted.is_type(event.type):
|
||||||
|
# STT start
|
||||||
|
await self.trigger_stt_start()
|
||||||
|
elif VoiceStopped.is_type(event.type):
|
||||||
|
# STT stop
|
||||||
|
await self.trigger_stt_stop()
|
||||||
|
elif Transcript.is_type(event.type):
|
||||||
|
# STT text
|
||||||
|
_LOGGER.debug(event)
|
||||||
|
await self.trigger_transcript(Transcript.from_event(event))
|
||||||
|
elif Synthesize.is_type(event.type):
|
||||||
|
# TTS request
|
||||||
|
_LOGGER.debug(event)
|
||||||
|
await self.trigger_synthesize(Synthesize.from_event(event))
|
||||||
|
elif Error.is_type(event.type):
|
||||||
|
_LOGGER.warning(event)
|
||||||
|
await self.trigger_error(Error.from_event(event))
|
||||||
|
|
||||||
|
# Forward everything except audio to event service
|
||||||
|
if not AudioChunk.is_type(event.type):
|
||||||
|
await self.forward_event(event)
|
||||||
|
|
||||||
async def event_to_server(self, event: Event) -> None:
|
async def event_to_server(self, event: Event) -> None:
|
||||||
"""Send an event to the server."""
|
"""Send an event to the server."""
|
||||||
@@ -154,10 +172,24 @@ class SatelliteBase:
|
|||||||
|
|
||||||
await async_write_event(event, self._writer)
|
await async_write_event(event, self._writer)
|
||||||
|
|
||||||
async def event_to_snd(self, event: Event) -> None:
|
async def _send_run_pipeline(self) -> None:
|
||||||
"""Send an event to the sound service."""
|
if self.settings.wake.enabled:
|
||||||
if self._snd_queue is not None:
|
# Local wake word detection
|
||||||
self._snd_queue.put_nowait(event)
|
start_stage = PipelineStage.ASR
|
||||||
|
else:
|
||||||
|
# Remote wake word detection
|
||||||
|
start_stage = PipelineStage.WAKE
|
||||||
|
|
||||||
|
if self.settings.snd.enabled:
|
||||||
|
# Play TTS response
|
||||||
|
end_stage = PipelineStage.TTS
|
||||||
|
else:
|
||||||
|
# No audio output
|
||||||
|
end_stage = PipelineStage.INTENT
|
||||||
|
|
||||||
|
run_pipeline = RunPipeline(start_stage=start_stage, end_stage=end_stage).event()
|
||||||
|
await self.event_to_server(run_pipeline)
|
||||||
|
await self.forward_event(run_pipeline)
|
||||||
|
|
||||||
async def _restart(self) -> None:
|
async def _restart(self) -> None:
|
||||||
self.state = State.RESTARTING
|
self.state = State.RESTARTING
|
||||||
@@ -189,19 +221,36 @@ class SatelliteBase:
|
|||||||
)
|
)
|
||||||
self._wake_task = asyncio.create_task(self._wake_task_proc())
|
self._wake_task = asyncio.create_task(self._wake_task_proc())
|
||||||
|
|
||||||
|
if self.settings.event.enabled:
|
||||||
|
_LOGGER.debug(
|
||||||
|
"Connecting to event service: %s",
|
||||||
|
self.settings.event.uri or self.settings.event.command,
|
||||||
|
)
|
||||||
|
self._event_task = asyncio.create_task(self._event_task_proc())
|
||||||
|
|
||||||
_LOGGER.info("Connected to services")
|
_LOGGER.info("Connected to services")
|
||||||
|
|
||||||
async def _disconnect_from_services(self) -> None:
|
async def _disconnect_from_services(self) -> None:
|
||||||
if self._mic_task is not None:
|
if self._mic_task is not None:
|
||||||
_LOGGER.debug("Stopping microphone")
|
_LOGGER.debug("Stopping microphone service")
|
||||||
self._mic_task.cancel()
|
self._mic_task.cancel()
|
||||||
self._mic_task = None
|
self._mic_task = None
|
||||||
|
|
||||||
if self._snd_task is not None:
|
if self._snd_task is not None:
|
||||||
_LOGGER.debug("Stopping sound")
|
_LOGGER.debug("Stopping sound service")
|
||||||
self._snd_task.cancel()
|
self._snd_task.cancel()
|
||||||
self._snd_task = None
|
self._snd_task = None
|
||||||
|
|
||||||
|
if self._wake_task is not None:
|
||||||
|
_LOGGER.debug("Stopping wake service")
|
||||||
|
self._wake_task.cancel()
|
||||||
|
self._wake_task = None
|
||||||
|
|
||||||
|
if self._event_task is not None:
|
||||||
|
_LOGGER.debug("Stopping event service")
|
||||||
|
self._event_task.cancel()
|
||||||
|
self._event_task = None
|
||||||
|
|
||||||
_LOGGER.debug("Disconnected from services")
|
_LOGGER.debug("Disconnected from services")
|
||||||
|
|
||||||
# -------------------------------------------------------------------------
|
# -------------------------------------------------------------------------
|
||||||
@@ -304,6 +353,11 @@ class SatelliteBase:
|
|||||||
# Sound
|
# Sound
|
||||||
# -------------------------------------------------------------------------
|
# -------------------------------------------------------------------------
|
||||||
|
|
||||||
|
async def event_to_snd(self, event: Event) -> None:
|
||||||
|
"""Send an event to the sound service."""
|
||||||
|
if self._snd_queue is not None:
|
||||||
|
self._snd_queue.put_nowait(event)
|
||||||
|
|
||||||
def _make_snd_client(self) -> Optional[AsyncClient]:
|
def _make_snd_client(self) -> Optional[AsyncClient]:
|
||||||
if self.settings.snd.command:
|
if self.settings.snd.command:
|
||||||
program, *program_args = self.settings.snd.command
|
program, *program_args = self.settings.snd.command
|
||||||
@@ -499,6 +553,70 @@ class SatelliteBase:
|
|||||||
await run_event_command(self.settings.event.transcript, transcript.text)
|
await run_event_command(self.settings.event.transcript, transcript.text)
|
||||||
await self._play_wav(self.settings.snd.done_wav)
|
await self._play_wav(self.settings.snd.done_wav)
|
||||||
|
|
||||||
|
async def trigger_stt_start(self) -> None:
|
||||||
|
await run_event_command(self.settings.event.stt_start)
|
||||||
|
|
||||||
|
async def trigger_stt_stop(self) -> None:
|
||||||
|
await run_event_command(self.settings.event.stt_stop)
|
||||||
|
|
||||||
|
async def trigger_synthesize(self, synthesize: Synthesize) -> None:
|
||||||
|
await run_event_command(self.settings.event.synthesize, synthesize.text)
|
||||||
|
|
||||||
|
async def trigger_tts_start(self) -> None:
|
||||||
|
await run_event_command(self.settings.event.tts_start)
|
||||||
|
|
||||||
|
async def trigger_tts_stop(self) -> None:
|
||||||
|
await run_event_command(self.settings.event.tts_stop)
|
||||||
|
|
||||||
|
async def trigger_error(self, error: Error) -> None:
|
||||||
|
await run_event_command(self.settings.event.error, error.text)
|
||||||
|
|
||||||
|
async def forward_event(self, event: Event) -> None:
|
||||||
|
"""Forward an event to the event service."""
|
||||||
|
if self._event_queue is not None:
|
||||||
|
self._event_queue.put_nowait(event)
|
||||||
|
|
||||||
|
def _make_event_client(self) -> Optional[AsyncClient]:
|
||||||
|
if self.settings.event.uri:
|
||||||
|
return AsyncClient.from_uri(self.settings.event.uri)
|
||||||
|
|
||||||
|
return None
|
||||||
|
|
||||||
|
async def _event_task_proc(self) -> None:
|
||||||
|
event_client: Optional[AsyncClient] = None
|
||||||
|
|
||||||
|
async def _disconnect() -> None:
|
||||||
|
try:
|
||||||
|
if event_client is not None:
|
||||||
|
await event_client.disconnect()
|
||||||
|
except Exception:
|
||||||
|
pass # ignore disconnect errors
|
||||||
|
|
||||||
|
while self.is_running:
|
||||||
|
try:
|
||||||
|
if self._event_queue is None:
|
||||||
|
self._event_queue = asyncio.Queue()
|
||||||
|
|
||||||
|
event = await self._event_queue.get()
|
||||||
|
|
||||||
|
if event_client is None:
|
||||||
|
event_client = self._make_event_client()
|
||||||
|
assert event_client is not None
|
||||||
|
await event_client.connect()
|
||||||
|
_LOGGER.debug("Connected to event service")
|
||||||
|
|
||||||
|
await event_client.write_event(event)
|
||||||
|
except asyncio.CancelledError:
|
||||||
|
break
|
||||||
|
except Exception:
|
||||||
|
_LOGGER.exception("Unexpected error in event read task")
|
||||||
|
await _disconnect()
|
||||||
|
event_client = None # reconnect
|
||||||
|
self._event_queue = None
|
||||||
|
await asyncio.sleep(self.settings.event.reconnect_seconds)
|
||||||
|
|
||||||
|
await _disconnect()
|
||||||
|
|
||||||
|
|
||||||
# -----------------------------------------------------------------------------
|
# -----------------------------------------------------------------------------
|
||||||
|
|
||||||
@@ -516,15 +634,8 @@ class AlwaysStreamingSatellite(SatelliteBase):
|
|||||||
if RunSatellite.is_type(event.type):
|
if RunSatellite.is_type(event.type):
|
||||||
self.is_streaming = True
|
self.is_streaming = True
|
||||||
_LOGGER.info("Streaming audio")
|
_LOGGER.info("Streaming audio")
|
||||||
|
await self._send_run_pipeline()
|
||||||
await self.trigger_streaming_start()
|
await self.trigger_streaming_start()
|
||||||
elif Detect.is_type(event.type):
|
|
||||||
await self.trigger_detect()
|
|
||||||
elif Detection.is_type(event.type):
|
|
||||||
_LOGGER.debug("Wake word detected")
|
|
||||||
await self.trigger_detection(Detection.from_event(event))
|
|
||||||
elif Transcript.is_type(event.type):
|
|
||||||
_LOGGER.debug(event)
|
|
||||||
await self.trigger_transcript(Transcript.from_event(event))
|
|
||||||
|
|
||||||
async def event_from_mic(
|
async def event_from_mic(
|
||||||
self, event: Event, audio_bytes: Optional[bytes] = None
|
self, event: Event, audio_bytes: Optional[bytes] = None
|
||||||
@@ -565,14 +676,6 @@ class VadStreamingSatellite(SatelliteBase):
|
|||||||
|
|
||||||
if RunSatellite.is_type(event.type):
|
if RunSatellite.is_type(event.type):
|
||||||
_LOGGER.info("Waiting for speech")
|
_LOGGER.info("Waiting for speech")
|
||||||
elif Detect.is_type(event.type):
|
|
||||||
await self.trigger_detect()
|
|
||||||
elif Detection.is_type(event.type):
|
|
||||||
_LOGGER.debug("Wake word detected")
|
|
||||||
await self.trigger_detection(Detection.from_event(event))
|
|
||||||
elif Transcript.is_type(event.type):
|
|
||||||
_LOGGER.debug(event)
|
|
||||||
await self.trigger_transcript(Transcript.from_event(event))
|
|
||||||
|
|
||||||
async def event_from_mic(
|
async def event_from_mic(
|
||||||
self, event: Event, audio_bytes: Optional[bytes] = None
|
self, event: Event, audio_bytes: Optional[bytes] = None
|
||||||
@@ -612,6 +715,7 @@ class VadStreamingSatellite(SatelliteBase):
|
|||||||
# Speech detected
|
# Speech detected
|
||||||
self.is_streaming = True
|
self.is_streaming = True
|
||||||
_LOGGER.info("Streaming audio")
|
_LOGGER.info("Streaming audio")
|
||||||
|
await self._send_run_pipeline()
|
||||||
await self.trigger_streaming_start()
|
await self.trigger_streaming_start()
|
||||||
|
|
||||||
if self.settings.vad.wake_word_timeout is not None:
|
if self.settings.vad.wake_word_timeout is not None:
|
||||||
@@ -666,9 +770,7 @@ class WakeStreamingSatellite(SatelliteBase):
|
|||||||
_LOGGER.info("Waiting for wake word")
|
_LOGGER.info("Waiting for wake word")
|
||||||
|
|
||||||
if Transcript.is_type(event.type):
|
if Transcript.is_type(event.type):
|
||||||
_LOGGER.debug(event)
|
|
||||||
await self.trigger_streaming_stop()
|
await self.trigger_streaming_stop()
|
||||||
await self.trigger_transcript(Transcript.from_event(event))
|
|
||||||
|
|
||||||
async def event_from_mic(
|
async def event_from_mic(
|
||||||
self, event: Event, audio_bytes: Optional[bytes] = None
|
self, event: Event, audio_bytes: Optional[bytes] = None
|
||||||
@@ -690,5 +792,6 @@ class WakeStreamingSatellite(SatelliteBase):
|
|||||||
if Detection.is_type(event.type):
|
if Detection.is_type(event.type):
|
||||||
self.is_streaming = True
|
self.is_streaming = True
|
||||||
_LOGGER.debug("Streaming audio")
|
_LOGGER.debug("Streaming audio")
|
||||||
|
await self._send_run_pipeline()
|
||||||
await self.trigger_detection(Detection.from_event(event))
|
await self.trigger_detection(Detection.from_event(event))
|
||||||
await self.trigger_streaming_start()
|
await self.trigger_streaming_start()
|
||||||
|
|||||||
Reference in New Issue
Block a user