More comments and adjustments

This commit is contained in:
Michael Hansen
2023-12-12 16:10:58 -06:00
parent 2e614ae443
commit e1eebbaf55
3 changed files with 101 additions and 11 deletions
+13 -8
View File
@@ -381,26 +381,31 @@ class SatelliteEventHandler(AsyncEventHandler):
self.client_id = str(time.monotonic_ns()) self.client_id = str(time.monotonic_ns())
self.satellite = satellite self.satellite = satellite
_LOGGER.debug("Client connected: %s", self.client_id)
self.satellite.set_server(self.client_id, self.writer)
# ------------------------------------------------------------------------- # -------------------------------------------------------------------------
async def handle_event(self, event: Event) -> bool: async def handle_event(self, event: Event) -> bool:
"""Handle events from the server.""" """Handle events from the server."""
if self.satellite.server_id != self.client_id:
# New connection
return False
if Describe.is_type(event.type): if Describe.is_type(event.type):
await self.write_event(self.wyoming_info_event) await self.write_event(self.wyoming_info_event)
_LOGGER.debug("Sent info to client: %s", self.client_id)
return True return True
if self.satellite.server_id is None:
# Take over after a problem occurred
self.satellite.set_server(self.client_id, self.writer)
elif self.satellite.server_id != self.client_id:
# New connection
_LOGGER.debug("Connection cancelled: %s", self.client_id)
return False
await self.satellite.event_from_server(event) await self.satellite.event_from_server(event)
return True return True
async def disconnect(self) -> None:
"""Server disconnect."""
if self.satellite.server_id == self.client_id:
self.satellite.clear_server()
# ----------------------------------------------------------------------------- # -----------------------------------------------------------------------------
+20 -3
View File
@@ -111,6 +111,13 @@ class SatelliteBase:
"""Set event writer.""" """Set event writer."""
self.server_id = server_id self.server_id = server_id
self._writer = writer self._writer = writer
_LOGGER.debug("Server set: %s", server_id)
def clear_server(self) -> None:
"""Remove writer."""
self.server_id = None
self._writer = None
_LOGGER.debug("Server disconnected")
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."""
@@ -119,9 +126,13 @@ class SatelliteBase:
try: try:
await async_write_event(event, self._writer) await async_write_event(event, self._writer)
except Exception: except Exception as err:
_LOGGER.exception("Unexpected error sending event to server") self.clear_server()
self._writer = None
if isinstance(err, ConnectionResetError):
_LOGGER.warning("Server disconnected unexpectedly")
else:
_LOGGER.exception("Unexpected error sending event to server")
# ------------------------------------------------------------------------- # -------------------------------------------------------------------------
@@ -442,6 +453,12 @@ class SatelliteBase:
).event() ).event()
await snd_client.write_event(event) await snd_client.write_event(event)
if self.settings.snd.disconnect_after_stop and AudioStop.is_type(
event.type
):
await _disconnect()
snd_client = None # reconnect on next event
except asyncio.CancelledError: except asyncio.CancelledError:
break break
except Exception: except Exception:
+68
View File
@@ -6,68 +6,134 @@ from typing import List, Optional
@dataclass(frozen=True) @dataclass(frozen=True)
class ServiceSettings(ABC): class ServiceSettings(ABC):
"""Base class for service settings."""
uri: Optional[str] = None uri: Optional[str] = None
"""tcp://ip-address:port"""
command: Optional[List[str]] = None command: Optional[List[str]] = None
"""program + args"""
reconnect_seconds: float = 3.0 reconnect_seconds: float = 3.0
"""Seconds before reconnection attempt is made."""
@property @property
def enabled(self) -> bool: def enabled(self) -> bool:
"""True if service is enabled."""
return bool(self.uri or self.command) return bool(self.uri or self.command)
@dataclass(frozen=True) @dataclass(frozen=True)
class MicSettings(ServiceSettings): class MicSettings(ServiceSettings):
"""Microphone service settings."""
volume_multiplier: float = 1.0 volume_multiplier: float = 1.0
"""Multiplier for raw audio data (1.0 = no change)"""
auto_gain: int = 0 auto_gain: int = 0
"""Automatic gain control (0-31 dbFS)"""
noise_suppression: int = 0 noise_suppression: int = 0
"""Level of noise suppression (0-4, 4 is max)"""
rate: int = 16000 rate: int = 16000
"""Sample rate of mic audio (hertz)"""
width: int = 2 width: int = 2
"""Sample width of mic audio (bytes)"""
channels: int = 1 channels: int = 1
"""Sample channels in mic audio"""
samples_per_chunk: int = 1024 samples_per_chunk: int = 1024
"""Samples to read at a time from mic command"""
@property @property
def needs_webrtc(self) -> bool: def needs_webrtc(self) -> bool:
"""True if webrtc audio enhancements are needed."""
return self.enabled and ((self.auto_gain > 0) or (self.noise_suppression > 0)) return self.enabled and ((self.auto_gain > 0) or (self.noise_suppression > 0))
@property @property
def needs_processing(self) -> bool: def needs_processing(self) -> bool:
"""True if some audio pre-processing is needed for mic audio."""
return self.enabled and ((self.volume_multiplier != 1.0) or self.needs_webrtc) return self.enabled and ((self.volume_multiplier != 1.0) or self.needs_webrtc)
@dataclass(frozen=True) @dataclass(frozen=True)
class SndSettings(ServiceSettings): class SndSettings(ServiceSettings):
"""Sound output service settings."""
volume_multiplier: float = 1.0 volume_multiplier: float = 1.0
"""Multiplier for raw audio data (1.0 = no change)"""
awake_wav: Optional[str] = None awake_wav: Optional[str] = None
"""Path to WAV file played after wake word detection."""
done_wav: Optional[str] = None done_wav: Optional[str] = None
"""Path to WAV file played after voice command is recognized."""
rate: int = 22050 rate: int = 22050
"""Sample rate of output audio (hertz)"""
width: int = 2 width: int = 2
"""Sample width of output audio (bytes)"""
channels: int = 1 channels: int = 1
"""Sample channels in output audio"""
samples_per_chunk: int = 1024 samples_per_chunk: int = 1024
"""Samples to write at a time to snd command"""
disconnect_after_stop: bool = True
"""True if snd service should be disconnected after AudioStop."""
@property @property
def needs_processing(self) -> bool: def needs_processing(self) -> bool:
"""True if some pre-processing is needed for output audio."""
return self.enabled and (self.volume_multiplier != 1.0) return self.enabled and (self.volume_multiplier != 1.0)
@dataclass(frozen=True) @dataclass(frozen=True)
class WakeSettings(ServiceSettings): class WakeSettings(ServiceSettings):
"""Wake word service settings."""
names: Optional[List[str]] = None names: Optional[List[str]] = None
"""List of wake word names to listen for."""
rate: int = 16000 rate: int = 16000
"""Sample rate of wake word audio (hertz)"""
width: int = 2 width: int = 2
"""Sample width of wake word audio (bytes)"""
channels: int = 1 channels: int = 1
"""Sample channels in wake word audio"""
@dataclass(frozen=True) @dataclass(frozen=True)
class VadSettings: class VadSettings:
"""Voice activity detector settings."""
enabled: bool = False enabled: bool = False
"""True if VAD should be used."""
threshold: float = 0.5 threshold: float = 0.5
"""Silence threshold (0-1, 1 is speech)"""
trigger_level: int = 1 trigger_level: int = 1
"""Number of chunks to cross threshold before activation."""
buffer_seconds: float = 2.0 buffer_seconds: float = 2.0
"""Seconds of audio before activation to keep/stream"""
wake_word_timeout: Optional[float] = 5.0 wake_word_timeout: Optional[float] = 5.0
"""Seconds before going back to sleep if wake word is not detected."""
@dataclass(frozen=True) @dataclass(frozen=True)
class EventSettings(ServiceSettings): class EventSettings(ServiceSettings):
"""External event service settings."""
startup: Optional[List[str]] = None startup: Optional[List[str]] = None
streaming_start: Optional[List[str]] = None streaming_start: Optional[List[str]] = None
streaming_stop: Optional[List[str]] = None streaming_stop: Optional[List[str]] = None
@@ -84,6 +150,8 @@ class EventSettings(ServiceSettings):
@dataclass(frozen=True) @dataclass(frozen=True)
class SatelliteSettings: class SatelliteSettings:
"""Wyoming satellite settings."""
mic: MicSettings mic: MicSettings
vad: VadSettings vad: VadSettings
wake: WakeSettings wake: WakeSettings