More comments and adjustments
This commit is contained in:
@@ -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()
|
||||||
|
|
||||||
|
|
||||||
# -----------------------------------------------------------------------------
|
# -----------------------------------------------------------------------------
|
||||||
|
|
||||||
|
|||||||
@@ -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:
|
||||||
|
self.clear_server()
|
||||||
|
|
||||||
|
if isinstance(err, ConnectionResetError):
|
||||||
|
_LOGGER.warning("Server disconnected unexpectedly")
|
||||||
|
else:
|
||||||
_LOGGER.exception("Unexpected error sending event to server")
|
_LOGGER.exception("Unexpected error sending event to server")
|
||||||
self._writer = None
|
|
||||||
|
|
||||||
# -------------------------------------------------------------------------
|
# -------------------------------------------------------------------------
|
||||||
|
|
||||||
@@ -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:
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
Reference in New Issue
Block a user