Add ping
This commit is contained in:
@@ -176,5 +176,7 @@ Satellites can respond to events from the server by running commands:
|
|||||||
* `--tts-start-command` - text-to-speech response started (no stdin)
|
* `--tts-start-command` - text-to-speech response started (no stdin)
|
||||||
* `--tts-stop-command` - text-to-speech response stopped (no stdin)
|
* `--tts-stop-command` - text-to-speech response stopped (no stdin)
|
||||||
* `--error-command` - an error was sent from the server (text on stdin)
|
* `--error-command` - an error was sent from the server (text on stdin)
|
||||||
|
* `--connected-command` - satellite connected to server
|
||||||
|
* `--disconnected-command` - satellite disconnected from server
|
||||||
|
|
||||||
For more advanced scenarios, use an event service (`--event-uri`). See `wyoming_satellite/example_event_client.py` for a basic client that just logs events.
|
For more advanced scenarios, use an event service (`--event-uri`). See `wyoming_satellite/example_event_client.py` for a basic client that just logs events.
|
||||||
|
|||||||
@@ -12,7 +12,13 @@ import gpiozero
|
|||||||
import spidev
|
import spidev
|
||||||
from wyoming.asr import Transcript
|
from wyoming.asr import Transcript
|
||||||
from wyoming.event import Event
|
from wyoming.event import Event
|
||||||
from wyoming.satellite import RunSatellite, StreamingStarted, StreamingStopped
|
from wyoming.satellite import (
|
||||||
|
RunSatellite,
|
||||||
|
StreamingStarted,
|
||||||
|
StreamingStopped,
|
||||||
|
SatelliteConnected,
|
||||||
|
SatelliteDisconnected,
|
||||||
|
)
|
||||||
from wyoming.server import AsyncEventHandler, AsyncServer
|
from wyoming.server import AsyncEventHandler, AsyncServer
|
||||||
from wyoming.vad import VoiceStarted
|
from wyoming.vad import VoiceStarted
|
||||||
from wyoming.wake import Detection
|
from wyoming.wake import Detection
|
||||||
@@ -107,6 +113,14 @@ class LEDsEventHandler(AsyncEventHandler):
|
|||||||
self.color(_BLACK)
|
self.color(_BLACK)
|
||||||
elif RunSatellite.is_type(event.type):
|
elif RunSatellite.is_type(event.type):
|
||||||
self.color(_BLACK)
|
self.color(_BLACK)
|
||||||
|
elif SatelliteConnected.is_type(event.type):
|
||||||
|
# Flash
|
||||||
|
for _ in range(5):
|
||||||
|
self.color(_GREEN)
|
||||||
|
await asyncio.sleep(0.3)
|
||||||
|
self.color(_BLACK)
|
||||||
|
elif SatelliteDisconnected.is_type(event.type):
|
||||||
|
self.color(_RED)
|
||||||
|
|
||||||
return True
|
return True
|
||||||
|
|
||||||
|
|||||||
@@ -12,7 +12,13 @@ import gpiozero
|
|||||||
import spidev
|
import spidev
|
||||||
from wyoming.asr import Transcript
|
from wyoming.asr import Transcript
|
||||||
from wyoming.event import Event
|
from wyoming.event import Event
|
||||||
from wyoming.satellite import RunSatellite, StreamingStarted, StreamingStopped
|
from wyoming.satellite import (
|
||||||
|
RunSatellite,
|
||||||
|
StreamingStarted,
|
||||||
|
StreamingStopped,
|
||||||
|
SatelliteConnected,
|
||||||
|
SatelliteDisconnected,
|
||||||
|
)
|
||||||
from wyoming.server import AsyncEventHandler, AsyncServer
|
from wyoming.server import AsyncEventHandler, AsyncServer
|
||||||
from wyoming.vad import VoiceStarted
|
from wyoming.vad import VoiceStarted
|
||||||
from wyoming.wake import Detection
|
from wyoming.wake import Detection
|
||||||
@@ -107,6 +113,14 @@ class LEDsEventHandler(AsyncEventHandler):
|
|||||||
self.color(_BLACK)
|
self.color(_BLACK)
|
||||||
elif RunSatellite.is_type(event.type):
|
elif RunSatellite.is_type(event.type):
|
||||||
self.color(_BLACK)
|
self.color(_BLACK)
|
||||||
|
elif SatelliteConnected.is_type(event.type):
|
||||||
|
# Flash
|
||||||
|
for _ in range(5):
|
||||||
|
self.color(_GREEN)
|
||||||
|
await asyncio.sleep(0.3)
|
||||||
|
self.color(_BLACK)
|
||||||
|
elif SatelliteDisconnected.is_type(event.type):
|
||||||
|
self.color(_RED)
|
||||||
|
|
||||||
return True
|
return True
|
||||||
|
|
||||||
|
|||||||
@@ -36,6 +36,7 @@ class SatelliteSettings(DataClassJsonMixin):
|
|||||||
name: str = "Wyoming Satellite"
|
name: str = "Wyoming Satellite"
|
||||||
type: SatelliteType = SatelliteType.ALWAYS_STREAMING
|
type: SatelliteType = SatelliteType.ALWAYS_STREAMING
|
||||||
debug: bool = False
|
debug: bool = False
|
||||||
|
event_service_command: Optional[List[str]] = None
|
||||||
|
|
||||||
|
|
||||||
@dataclass
|
@dataclass
|
||||||
|
|||||||
+38
-10
@@ -1,7 +1,7 @@
|
|||||||
"""Satellite settings."""
|
"""Satellite settings."""
|
||||||
from typing import Optional
|
from typing import Optional
|
||||||
|
|
||||||
from .const import SatelliteType, Settings
|
from .const import SatelliteType, Settings, PROGRAM_DIR
|
||||||
from .whiptail import error, inputbox, menu, passwordbox, radiolist, run_with_gauge
|
from .whiptail import error, inputbox, menu, passwordbox, radiolist, run_with_gauge
|
||||||
|
|
||||||
|
|
||||||
@@ -29,20 +29,22 @@ def configure_satellite(settings: Settings) -> None:
|
|||||||
if satellite_type is not None:
|
if satellite_type is not None:
|
||||||
settings.satellite.type = SatelliteType(satellite_type)
|
settings.satellite.type = SatelliteType(satellite_type)
|
||||||
settings.save()
|
settings.save()
|
||||||
elif choice in ("stop", "start"):
|
elif choice == "feedback":
|
||||||
|
configure_feedback(settings)
|
||||||
|
elif choice in ("restart", "stop", "start"):
|
||||||
password = passwordbox("sudo password:")
|
password = passwordbox("sudo password:")
|
||||||
if not password:
|
if not password:
|
||||||
continue
|
continue
|
||||||
|
|
||||||
command = ["sudo", "-S", "systemctl", choice, "wyoming-satellite.service"]
|
command = ["sudo", "-S", "systemctl", choice, "wyoming-satellite.service"]
|
||||||
text = (
|
text = {"restart": "Restarting", "stop": "Stopping", "start": "Starting"}[
|
||||||
"Stopping satellite..." if choice == "stop" else "Starting satellite..."
|
choice
|
||||||
|
]
|
||||||
|
success = run_with_gauge(
|
||||||
|
f"{text} satellite...", [command], sudo_password=password
|
||||||
)
|
)
|
||||||
success = run_with_gauge(text, [command], sudo_password=password)
|
|
||||||
if not success:
|
if not success:
|
||||||
error(
|
error(f"{text.lower()} satellite")
|
||||||
"stopping satellite" if choice == "stop" else "starting satellite"
|
|
||||||
)
|
|
||||||
elif choice == "debug":
|
elif choice == "debug":
|
||||||
debug = radiolist(
|
debug = radiolist(
|
||||||
"Debug Mode:",
|
"Debug Mode:",
|
||||||
@@ -66,10 +68,36 @@ def satellite_menu(last_choice: Optional[str]) -> Optional[str]:
|
|||||||
[
|
[
|
||||||
("name", "Satellite Name"),
|
("name", "Satellite Name"),
|
||||||
("type", "Satellite Type"),
|
("type", "Satellite Type"),
|
||||||
("stop", "Stop Service"),
|
("feedback", "Feedback"),
|
||||||
("start", "Start Service"),
|
("restart", "Restart Services"),
|
||||||
|
("stop", "Stop Services"),
|
||||||
|
("start", "Start Services"),
|
||||||
("debug", "Set Debug Mode"),
|
("debug", "Set Debug Mode"),
|
||||||
],
|
],
|
||||||
selected_item=last_choice,
|
selected_item=last_choice,
|
||||||
menu_args=["--ok-button", "Select", "--cancel-button", "Back"],
|
menu_args=["--ok-button", "Select", "--cancel-button", "Back"],
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def configure_feedback(settings: Settings) -> None:
|
||||||
|
choice: Optional[str] = None
|
||||||
|
while True:
|
||||||
|
choice = menu("Main > Satellite > Feedback", [("respeaker", "ReSpeaker")])
|
||||||
|
if choice == "respeaker":
|
||||||
|
event_service = radiolist(
|
||||||
|
"Event Service:",
|
||||||
|
[
|
||||||
|
("2mic", "2mic LEDs"),
|
||||||
|
("4mic", "4mic LEDs"),
|
||||||
|
],
|
||||||
|
)
|
||||||
|
|
||||||
|
if event_service is not None:
|
||||||
|
settings.satellite.event_service_command = [
|
||||||
|
str(PROGRAM_DIR / "script" / "run_{event_service}"),
|
||||||
|
"--uri",
|
||||||
|
"tcp://127.0.0.1:10500",
|
||||||
|
]
|
||||||
|
settings.save()
|
||||||
|
else:
|
||||||
|
break
|
||||||
|
|||||||
@@ -158,6 +158,29 @@ def generate_services(settings: Settings) -> None:
|
|||||||
)
|
)
|
||||||
satellite_requires.append(f"{wake_word_service}.service")
|
satellite_requires.append(f"{wake_word_service}.service")
|
||||||
|
|
||||||
|
if settings.satellite.event_service_command:
|
||||||
|
event_service = "wyoming-event"
|
||||||
|
event_command_str = shlex.join(settings.satellite.event_service_command)
|
||||||
|
with open(
|
||||||
|
SERVICES_DIR / f"{event_service}.service", "w", encoding="utf-8"
|
||||||
|
) as service_file:
|
||||||
|
print("[Unit]", file=service_file)
|
||||||
|
print(f"Description=Event service", file=service_file)
|
||||||
|
print("", file=service_file)
|
||||||
|
print("[Service]", file=service_file)
|
||||||
|
print("Type=simple", file=service_file)
|
||||||
|
print(f"User={user_name}", file=service_file)
|
||||||
|
print(f"ExecStart={event_command_str}", file=service_file)
|
||||||
|
print(f"WorkingDirectory={PROGRAM_DIR}", file=service_file)
|
||||||
|
print("Restart=always", file=service_file)
|
||||||
|
print("RestartSec=1", file=service_file)
|
||||||
|
print("", file=service_file)
|
||||||
|
print("[Install]", file=service_file)
|
||||||
|
print("WantedBy=default.target", file=service_file)
|
||||||
|
|
||||||
|
satellite_command.extend(["--event-uri", "tcp://127.0.0.1:10500"])
|
||||||
|
satellite_requires.append(f"{event_service}.service")
|
||||||
|
|
||||||
if settings.satellite.debug:
|
if settings.satellite.debug:
|
||||||
satellite_command.extend(
|
satellite_command.extend(
|
||||||
["--debug", "--debug-recording-dir", str(LOCAL_DIR / "debug-recording")]
|
["--debug", "--debug-recording-dir", str(LOCAL_DIR / "debug-recording")]
|
||||||
|
|||||||
+2
-1
@@ -9,8 +9,9 @@ _VENV_DIR = _PROGRAM_DIR / ".venv"
|
|||||||
_MODULE_DIR = _PROGRAM_DIR / "wyoming_satellite"
|
_MODULE_DIR = _PROGRAM_DIR / "wyoming_satellite"
|
||||||
_TESTS_DIR = _PROGRAM_DIR / "tests"
|
_TESTS_DIR = _PROGRAM_DIR / "tests"
|
||||||
_INSTALLER_DIR = _PROGRAM_DIR / "installer"
|
_INSTALLER_DIR = _PROGRAM_DIR / "installer"
|
||||||
|
_EXAMPLES_DIR = _PROGRAM_DIR / "examples"
|
||||||
|
|
||||||
_FORMAT_DIRS = [_MODULE_DIR, _TESTS_DIR, _INSTALLER_DIR]
|
_FORMAT_DIRS = [_MODULE_DIR, _TESTS_DIR, _INSTALLER_DIR, _EXAMPLES_DIR]
|
||||||
|
|
||||||
context = venv.EnvBuilder().ensure_directories(_VENV_DIR)
|
context = venv.EnvBuilder().ensure_directories(_VENV_DIR)
|
||||||
subprocess.check_call([context.env_exe, "-m", "black"] + _FORMAT_DIRS)
|
subprocess.check_call([context.env_exe, "-m", "black"] + _FORMAT_DIRS)
|
||||||
|
|||||||
Executable
+15
@@ -0,0 +1,15 @@
|
|||||||
|
#!/usr/bin/env python3
|
||||||
|
import sys
|
||||||
|
import subprocess
|
||||||
|
import venv
|
||||||
|
from pathlib import Path
|
||||||
|
|
||||||
|
_DIR = Path(__file__).parent
|
||||||
|
_PROGRAM_DIR = _DIR.parent
|
||||||
|
_VENV_DIR = _PROGRAM_DIR / ".venv"
|
||||||
|
|
||||||
|
context = venv.EnvBuilder().ensure_directories(_VENV_DIR)
|
||||||
|
subprocess.check_call(
|
||||||
|
[context.env_exe, str(_PROGRAM_DIR / "examples" / "2mic_service.py")]
|
||||||
|
+ sys.argv[1:]
|
||||||
|
)
|
||||||
Executable
+15
@@ -0,0 +1,15 @@
|
|||||||
|
#!/usr/bin/env python3
|
||||||
|
import sys
|
||||||
|
import subprocess
|
||||||
|
import venv
|
||||||
|
from pathlib import Path
|
||||||
|
|
||||||
|
_DIR = Path(__file__).parent
|
||||||
|
_PROGRAM_DIR = _DIR.parent
|
||||||
|
_VENV_DIR = _PROGRAM_DIR / ".venv"
|
||||||
|
|
||||||
|
context = venv.EnvBuilder().ensure_directories(_VENV_DIR)
|
||||||
|
subprocess.check_call(
|
||||||
|
[context.env_exe, str(_PROGRAM_DIR / "examples" / "4mic_service.py")]
|
||||||
|
+ sys.argv[1:]
|
||||||
|
)
|
||||||
@@ -35,7 +35,7 @@ extras_require = {
|
|||||||
|
|
||||||
setup(
|
setup(
|
||||||
name="wyoming_satellite",
|
name="wyoming_satellite",
|
||||||
version="1.0.1",
|
version="1.1.0",
|
||||||
description="Wyoming server for remote voice satellite",
|
description="Wyoming server for remote voice satellite",
|
||||||
url="http://github.com/rhasspy/wyoming-satellite",
|
url="http://github.com/rhasspy/wyoming-satellite",
|
||||||
author="Michael Hansen",
|
author="Michael Hansen",
|
||||||
|
|||||||
@@ -193,6 +193,14 @@ async def main() -> None:
|
|||||||
"--error-command",
|
"--error-command",
|
||||||
help="Command to run when an error occurs",
|
help="Command to run when an error occurs",
|
||||||
)
|
)
|
||||||
|
parser.add_argument(
|
||||||
|
"--connected-command",
|
||||||
|
help="Command to run when connected to the server",
|
||||||
|
)
|
||||||
|
parser.add_argument(
|
||||||
|
"--disconnected-command",
|
||||||
|
help="Command to run when disconnected from the server",
|
||||||
|
)
|
||||||
|
|
||||||
# Sounds
|
# Sounds
|
||||||
parser.add_argument(
|
parser.add_argument(
|
||||||
@@ -326,6 +334,8 @@ async def main() -> None:
|
|||||||
tts_start=split_command(args.tts_start_command),
|
tts_start=split_command(args.tts_start_command),
|
||||||
tts_stop=split_command(args.tts_stop_command),
|
tts_stop=split_command(args.tts_stop_command),
|
||||||
error=split_command(args.error_command),
|
error=split_command(args.error_command),
|
||||||
|
connected=split_command(args.connected_command),
|
||||||
|
disconnected=split_command(args.disconnected_command),
|
||||||
),
|
),
|
||||||
debug_recording_dir=args.debug_recording_dir,
|
debug_recording_dir=args.debug_recording_dir,
|
||||||
)
|
)
|
||||||
@@ -368,7 +378,7 @@ async def main() -> None:
|
|||||||
args.zeroconf_host,
|
args.zeroconf_host,
|
||||||
)
|
)
|
||||||
|
|
||||||
satellite_task = asyncio.create_task(satellite.run())
|
satellite_task = asyncio.create_task(satellite.run(), name="satellite run")
|
||||||
|
|
||||||
try:
|
try:
|
||||||
await server.run(partial(SatelliteEventHandler, wyoming_info, satellite, args))
|
await server.run(partial(SatelliteEventHandler, wyoming_info, satellite, args))
|
||||||
|
|||||||
@@ -40,7 +40,7 @@ class SatelliteEventHandler(AsyncEventHandler):
|
|||||||
|
|
||||||
if self.satellite.server_id is None:
|
if self.satellite.server_id is None:
|
||||||
# Take over after a problem occurred
|
# Take over after a problem occurred
|
||||||
self.satellite.set_server(self.client_id, self.writer)
|
await self.satellite.set_server(self.client_id, self.writer)
|
||||||
elif self.satellite.server_id != self.client_id:
|
elif self.satellite.server_id != self.client_id:
|
||||||
# New connection
|
# New connection
|
||||||
_LOGGER.debug("Connection cancelled: %s", self.client_id)
|
_LOGGER.debug("Connection cancelled: %s", self.client_id)
|
||||||
@@ -53,4 +53,4 @@ class SatelliteEventHandler(AsyncEventHandler):
|
|||||||
async def disconnect(self) -> None:
|
async def disconnect(self) -> None:
|
||||||
"""Server disconnect."""
|
"""Server disconnect."""
|
||||||
if self.satellite.server_id == self.client_id:
|
if self.satellite.server_id == self.client_id:
|
||||||
self.satellite.clear_server()
|
await self.satellite.clear_server()
|
||||||
|
|||||||
@@ -5,7 +5,7 @@ import math
|
|||||||
import time
|
import time
|
||||||
from enum import Enum, auto
|
from enum import Enum, auto
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
from typing import Callable, Dict, Optional, Set, Union
|
from typing import Callable, Dict, Optional, Set, Union, Final
|
||||||
|
|
||||||
from pyring_buffer import RingBuffer
|
from pyring_buffer import RingBuffer
|
||||||
from wyoming.asr import Transcript
|
from wyoming.asr import Transcript
|
||||||
@@ -15,11 +15,18 @@ from wyoming.error import Error
|
|||||||
from wyoming.event import Event, async_write_event
|
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, StreamingStarted, StreamingStopped
|
from wyoming.satellite import (
|
||||||
|
RunSatellite,
|
||||||
|
StreamingStarted,
|
||||||
|
StreamingStopped,
|
||||||
|
SatelliteConnected,
|
||||||
|
SatelliteDisconnected,
|
||||||
|
)
|
||||||
from wyoming.snd import SndProcessAsyncClient
|
from wyoming.snd import SndProcessAsyncClient
|
||||||
from wyoming.tts import Synthesize
|
from wyoming.tts import Synthesize
|
||||||
from wyoming.vad import VoiceStarted, VoiceStopped
|
from wyoming.vad import VoiceStarted, VoiceStopped
|
||||||
from wyoming.wake import Detect, Detection, WakeProcessAsyncClient
|
from wyoming.wake import Detect, Detection, WakeProcessAsyncClient
|
||||||
|
from wyoming.ping import Ping, Pong
|
||||||
|
|
||||||
from .settings import SatelliteSettings
|
from .settings import SatelliteSettings
|
||||||
from .utils import DebugAudioWriter, multiply_volume, run_event_command, wav_to_events
|
from .utils import DebugAudioWriter, multiply_volume, run_event_command, wav_to_events
|
||||||
@@ -28,6 +35,9 @@ from .webrtc import WebRtcAudio
|
|||||||
|
|
||||||
_LOGGER = logging.getLogger()
|
_LOGGER = logging.getLogger()
|
||||||
|
|
||||||
|
_PONG_TIMEOUT: Final = 5
|
||||||
|
_PING_SEND_DELAY: Final = 2
|
||||||
|
|
||||||
|
|
||||||
class State(Enum):
|
class State(Enum):
|
||||||
NOT_STARTED = auto()
|
NOT_STARTED = auto()
|
||||||
@@ -60,6 +70,9 @@ class SatelliteBase:
|
|||||||
self._event_task: Optional[asyncio.Task] = None
|
self._event_task: Optional[asyncio.Task] = None
|
||||||
self._event_queue: "Optional[asyncio.Queue[Event]]" = None
|
self._event_queue: "Optional[asyncio.Queue[Event]]" = None
|
||||||
|
|
||||||
|
self._pong_received_event = asyncio.Event()
|
||||||
|
self._ping_server_task = asyncio.create_task(self._ping_server(), name="ping")
|
||||||
|
|
||||||
# Debug audio recording
|
# Debug audio recording
|
||||||
self.wake_audio_writer: Optional[DebugAudioWriter] = None
|
self.wake_audio_writer: Optional[DebugAudioWriter] = None
|
||||||
self.stt_audio_writer: Optional[DebugAudioWriter] = None
|
self.stt_audio_writer: Optional[DebugAudioWriter] = None
|
||||||
@@ -120,17 +133,19 @@ class SatelliteBase:
|
|||||||
while self.state != State.STOPPED:
|
while self.state != State.STOPPED:
|
||||||
await self._state_changed.wait()
|
await self._state_changed.wait()
|
||||||
|
|
||||||
def set_server(self, server_id: str, writer: asyncio.StreamWriter) -> None:
|
async def set_server(self, server_id: str, writer: asyncio.StreamWriter) -> None:
|
||||||
"""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)
|
_LOGGER.debug("Server set: %s", server_id)
|
||||||
|
await self.trigger_server_connected()
|
||||||
|
|
||||||
def clear_server(self) -> None:
|
async def clear_server(self) -> None:
|
||||||
"""Remove writer."""
|
"""Remove writer."""
|
||||||
self.server_id = None
|
self.server_id = None
|
||||||
self._writer = None
|
self._writer = None
|
||||||
_LOGGER.debug("Server disconnected")
|
_LOGGER.debug("Server disconnected")
|
||||||
|
await self.trigger_server_disonnected()
|
||||||
|
|
||||||
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."""
|
||||||
@@ -147,6 +162,31 @@ class SatelliteBase:
|
|||||||
else:
|
else:
|
||||||
_LOGGER.exception("Unexpected error sending event to server")
|
_LOGGER.exception("Unexpected error sending event to server")
|
||||||
|
|
||||||
|
async def _ping_server(self) -> None:
|
||||||
|
try:
|
||||||
|
while self.is_running:
|
||||||
|
await asyncio.sleep(_PING_SEND_DELAY)
|
||||||
|
if self.server_id is None:
|
||||||
|
# No server connected
|
||||||
|
continue
|
||||||
|
|
||||||
|
# Send ping and wait for pong
|
||||||
|
self._pong_received_event.clear()
|
||||||
|
await self.event_to_server(Ping().event())
|
||||||
|
try:
|
||||||
|
await asyncio.wait_for(
|
||||||
|
self._pong_received_event.wait(), timeout=_PONG_TIMEOUT
|
||||||
|
)
|
||||||
|
except asyncio.TimeoutError:
|
||||||
|
if self.server_id is None:
|
||||||
|
# No server connected
|
||||||
|
continue
|
||||||
|
|
||||||
|
_LOGGER.warning("Did not receive ping response within timeout")
|
||||||
|
await self.clear_server()
|
||||||
|
except Exception:
|
||||||
|
_LOGGER.exception("Unexpected error in ping server task")
|
||||||
|
|
||||||
# -------------------------------------------------------------------------
|
# -------------------------------------------------------------------------
|
||||||
|
|
||||||
async def _start(self) -> None:
|
async def _start(self) -> None:
|
||||||
@@ -172,7 +212,14 @@ 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 AudioChunk.is_type(event.type):
|
if Ping.is_type(event.type):
|
||||||
|
# Respond with pong
|
||||||
|
ping = Ping.from_event(event)
|
||||||
|
await self.event_to_server(Pong(text=ping.text).event())
|
||||||
|
elif Pong.is_type(event.type):
|
||||||
|
# Response from our ping
|
||||||
|
self._pong_received_event.set()
|
||||||
|
elif AudioChunk.is_type(event.type):
|
||||||
# TTS audio
|
# TTS audio
|
||||||
await self.event_to_snd(event)
|
await self.event_to_snd(event)
|
||||||
elif AudioStart.is_type(event.type):
|
elif AudioStart.is_type(event.type):
|
||||||
@@ -217,9 +264,11 @@ class SatelliteBase:
|
|||||||
if self.settings.wake.enabled:
|
if self.settings.wake.enabled:
|
||||||
# Local wake word detection
|
# Local wake word detection
|
||||||
start_stage = PipelineStage.ASR
|
start_stage = PipelineStage.ASR
|
||||||
|
restart_on_end = False
|
||||||
else:
|
else:
|
||||||
# Remote wake word detection
|
# Remote wake word detection
|
||||||
start_stage = PipelineStage.WAKE
|
start_stage = PipelineStage.WAKE
|
||||||
|
restart_on_end = not self.settings.vad.enabled
|
||||||
|
|
||||||
if self.settings.snd.enabled:
|
if self.settings.snd.enabled:
|
||||||
# Play TTS response
|
# Play TTS response
|
||||||
@@ -228,7 +277,9 @@ class SatelliteBase:
|
|||||||
# No audio output
|
# No audio output
|
||||||
end_stage = PipelineStage.HANDLE
|
end_stage = PipelineStage.HANDLE
|
||||||
|
|
||||||
run_pipeline = RunPipeline(start_stage=start_stage, end_stage=end_stage).event()
|
run_pipeline = RunPipeline(
|
||||||
|
start_stage=start_stage, end_stage=end_stage, restart_on_end=restart_on_end
|
||||||
|
).event()
|
||||||
await self.event_to_server(run_pipeline)
|
await self.event_to_server(run_pipeline)
|
||||||
await self.forward_event(run_pipeline)
|
await self.forward_event(run_pipeline)
|
||||||
|
|
||||||
@@ -639,6 +690,18 @@ class SatelliteBase:
|
|||||||
# Events
|
# Events
|
||||||
# -------------------------------------------------------------------------
|
# -------------------------------------------------------------------------
|
||||||
|
|
||||||
|
async def trigger_server_connected(self) -> None:
|
||||||
|
"""Called when connected to server."""
|
||||||
|
_LOGGER.info("Connected to server")
|
||||||
|
await run_event_command(self.settings.event.connected)
|
||||||
|
await self.forward_event(SatelliteConnected().event())
|
||||||
|
|
||||||
|
async def trigger_server_disonnected(self) -> None:
|
||||||
|
"""Called when disconnected from server."""
|
||||||
|
_LOGGER.info("Disconnected from server")
|
||||||
|
await run_event_command(self.settings.event.disconnected)
|
||||||
|
await self.forward_event(SatelliteDisconnected().event())
|
||||||
|
|
||||||
async def trigger_streaming_start(self) -> None:
|
async def trigger_streaming_start(self) -> None:
|
||||||
"""Called when audio streaming starts."""
|
"""Called when audio streaming starts."""
|
||||||
await run_event_command(self.settings.event.streaming_start)
|
await run_event_command(self.settings.event.streaming_start)
|
||||||
@@ -997,6 +1060,17 @@ class WakeStreamingSatellite(SatelliteBase):
|
|||||||
if self.wake_audio_writer is not None:
|
if self.wake_audio_writer is not None:
|
||||||
self.wake_audio_writer.start(timestamp=self._debug_recording_timestamp)
|
self.wake_audio_writer.start(timestamp=self._debug_recording_timestamp)
|
||||||
|
|
||||||
|
async def trigger_server_disonnected(self) -> None:
|
||||||
|
await super().trigger_server_disonnected()
|
||||||
|
|
||||||
|
self.is_streaming = False
|
||||||
|
|
||||||
|
# Stop debug recording (stt)
|
||||||
|
if self.stt_audio_writer is not None:
|
||||||
|
self.stt_audio_writer.stop()
|
||||||
|
|
||||||
|
await self.trigger_streaming_stop()
|
||||||
|
|
||||||
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
|
||||||
) -> None:
|
) -> None:
|
||||||
@@ -1023,7 +1097,8 @@ class WakeStreamingSatellite(SatelliteBase):
|
|||||||
await self.event_to_wake(event)
|
await self.event_to_wake(event)
|
||||||
|
|
||||||
async def event_from_wake(self, event: Event) -> None:
|
async def event_from_wake(self, event: Event) -> None:
|
||||||
if self.is_streaming:
|
if self.is_streaming or (self.server_id is None):
|
||||||
|
# Not streaming or no server connected
|
||||||
return
|
return
|
||||||
|
|
||||||
if Detection.is_type(event.type):
|
if Detection.is_type(event.type):
|
||||||
|
|||||||
@@ -150,6 +150,8 @@ class EventSettings(ServiceSettings):
|
|||||||
tts_start: Optional[List[str]] = None
|
tts_start: Optional[List[str]] = None
|
||||||
tts_stop: Optional[List[str]] = None
|
tts_stop: Optional[List[str]] = None
|
||||||
error: Optional[List[str]] = None
|
error: Optional[List[str]] = None
|
||||||
|
connected: Optional[List[str]] = None
|
||||||
|
disconnected: Optional[List[str]] = None
|
||||||
|
|
||||||
|
|
||||||
@dataclass(frozen=True)
|
@dataclass(frozen=True)
|
||||||
|
|||||||
Reference in New Issue
Block a user