Add initial test

This commit is contained in:
Michael Hansen
2024-01-09 12:02:05 -06:00
parent 172a85846c
commit e7dcbf4383
8 changed files with 203 additions and 128 deletions
+7
View File
@@ -123,6 +123,12 @@ async def main() -> None:
default=1,
help="Sample channels of wake-command (default: 1)",
)
parser.add_argument(
"--wake-refractory-seconds",
type=float,
default=5.0,
help="Seconds after a wake word detection before another detection is handled (default: 5)",
)
# Voice activity detector
parser.add_argument(
@@ -285,6 +291,7 @@ async def main() -> None:
uri=args.wake_uri,
command=split_command(args.wake_command),
names=args.wake_word_name,
refractory_seconds=args.wake_refractory_seconds,
),
snd=SndSettings(
uri=args.snd_uri,
+62 -8
View File
@@ -5,7 +5,7 @@ import math
import time
from enum import Enum, auto
from pathlib import Path
from typing import Callable, Optional, Set, Union
from typing import Callable, Dict, Optional, Set, Union
from pyring_buffer import RingBuffer
from wyoming.asr import Transcript
@@ -63,7 +63,7 @@ class SatelliteBase:
@property
def is_running(self) -> bool:
"""True if not stopping/stopped."""
return self._state not in (State.STOPPING, State.STOPPED)
return self._state not in (State.STOPPED,)
@property
def state(self) -> State:
@@ -235,28 +235,30 @@ class SatelliteBase:
"Connecting to mic service: %s",
self.settings.mic.uri or self.settings.mic.command,
)
self._mic_task = asyncio.create_task(self._mic_task_proc())
self._mic_task = asyncio.create_task(self._mic_task_proc(), name="mic")
if self.settings.snd.enabled:
_LOGGER.debug(
"Connecting to snd service: %s",
self.settings.snd.uri or self.settings.snd.command,
)
self._snd_task = asyncio.create_task(self._snd_task_proc())
self._snd_task = asyncio.create_task(self._snd_task_proc(), name="snd")
if self.settings.wake.enabled:
_LOGGER.debug(
"Connecting to wake service: %s",
self.settings.wake.uri or self.settings.wake.command,
)
self._wake_task = asyncio.create_task(self._wake_task_proc())
self._wake_task = asyncio.create_task(self._wake_task_proc(), name="wake")
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())
self._event_task = asyncio.create_task(
self._event_task_proc(), name="event"
)
_LOGGER.info("Connected to services")
@@ -528,9 +530,19 @@ class SatelliteBase:
pending: Set[asyncio.Task] = set()
async def _disconnect() -> None:
nonlocal to_client_task, from_client_task
try:
if wake_client is not None:
await wake_client.disconnect()
# Clean up tasks
if to_client_task is not None:
to_client_task.cancel()
to_client_task = None
if from_client_task is not None:
from_client_task.cancel()
from_client_task = None
except Exception:
pass # ignore disconnect errors
@@ -557,12 +569,16 @@ class SatelliteBase:
# Read/write in "parallel"
if to_client_task is None:
# From satellite to wake service
to_client_task = asyncio.create_task(self._wake_queue.get())
to_client_task = asyncio.create_task(
self._wake_queue.get(), name="wake_to_client"
)
pending.add(to_client_task)
if from_client_task is None:
# From wake service to satellite
from_client_task = asyncio.create_task(wake_client.read_event())
from_client_task = asyncio.create_task(
wake_client.read_event(), name="wake_from_client"
)
pending.add(from_client_task)
done, pending = await asyncio.wait(
@@ -717,6 +733,12 @@ class AlwaysStreamingSatellite(SatelliteBase):
super().__init__(settings)
self.is_streaming = False
if settings.vad.enabled:
_LOGGER.warning("VAD is enabled but will not be used")
if settings.wake.enabled:
_LOGGER.warning("Local wake word detection is enabled but will not be used")
async def event_from_server(self, event: Event) -> None:
await super().event_from_server(event)
@@ -771,6 +793,9 @@ class VadStreamingSatellite(SatelliteBase):
vad_buffer_bytes = int(math.ceil(settings.vad.buffer_seconds * 16000 * 2))
self.vad_buffer = RingBuffer(maxlen=vad_buffer_bytes)
if settings.wake.enabled:
_LOGGER.warning("Local wake word detection is enabled but will not be used")
async def event_from_server(self, event: Event) -> None:
await super().event_from_server(event)
@@ -872,6 +897,14 @@ class WakeStreamingSatellite(SatelliteBase):
super().__init__(settings)
self.is_streaming = False
# Timestamp in the future when the refractory period is over (set with
# time.monotonic()).
# wake word id -> seconds
self.refractory_timestamp: Dict[Optional[str], float] = {}
if settings.vad.enabled:
_LOGGER.warning("VAD is enabled but will not be used")
async def event_from_server(self, event: Event) -> None:
if Transcript.is_type(event.type):
# Stop streaming before event_from_server is called because it will
@@ -904,8 +937,29 @@ class WakeStreamingSatellite(SatelliteBase):
return
if Detection.is_type(event.type):
detection = Detection.from_event(event)
# Check refractory period to avoid multiple back-to-back detections
refractory_timestamp = self.refractory_timestamp.get(detection.name)
if (refractory_timestamp is not None) and (
refractory_timestamp > time.monotonic()
):
_LOGGER.debug("Wake word detection occurred during refractory period")
return
self.is_streaming = True
_LOGGER.debug("Streaming audio")
if self.settings.wake.refractory_seconds is not None:
# Another detection may not occur for this wake word until
# refractory period is over.
self.refractory_timestamp[detection.name] = (
time.monotonic() + self.settings.wake.refractory_seconds
)
else:
# No refractory period
self.refractory_timestamp.pop(detection.name, None)
await self._send_run_pipeline()
await self.forward_event(event) # forward to event service
await self.trigger_detection(Detection.from_event(event))
+3
View File
@@ -109,6 +109,9 @@ class WakeSettings(ServiceSettings):
channels: int = 1
"""Sample channels in wake word audio"""
refractory_seconds: Optional[float] = 5.0
"""Seconds after a wake word detection before another detection is handled."""
@dataclass(frozen=True)
class VadSettings: