diff --git a/.gitignore b/.gitignore index f0c0a12..8798bff 100644 --- a/.gitignore +++ b/.gitignore @@ -9,7 +9,7 @@ tmp/ build htmlcov -/.venv/ +.venv/ .mypy_cache/ __pycache__/ diff --git a/examples/websocket-service/requirements.txt b/examples/websocket-service/requirements.txt new file mode 100644 index 0000000..02e1832 --- /dev/null +++ b/examples/websocket-service/requirements.txt @@ -0,0 +1 @@ +websockets==12.0 diff --git a/examples/websocket-service/server.py b/examples/websocket-service/server.py new file mode 100644 index 0000000..6fbf84c --- /dev/null +++ b/examples/websocket-service/server.py @@ -0,0 +1,100 @@ +#!/usr/bin/env python3 +import argparse +import asyncio +import json +import logging +from functools import partial +from typing import Optional + +import websockets +from wyoming.event import Event +from wyoming.server import AsyncEventHandler, AsyncServer + +_LOGGER = logging.getLogger() + + +async def main() -> None: + """Main entry point.""" + parser = argparse.ArgumentParser() + parser.add_argument("--uri", required=True, help="unix:// or tcp://") + parser.add_argument("--websocket-host", default="localhost") + parser.add_argument("--websocket-port", type=int, default=8675) + # + parser.add_argument("--debug", action="store_true", help="Log DEBUG messages") + args = parser.parse_args() + + logging.basicConfig(level=logging.DEBUG if args.debug else logging.INFO) + _LOGGER.debug(args) + + _LOGGER.info("Ready") + + # Start server + server = AsyncServer.from_uri(args.uri) + queue: "asyncio.Queue[Optional[Event]]" = asyncio.Queue() + + try: + async with websockets.serve( + partial(websocket_connected, queue), + args.websocket_host, + args.websocket_port, + ): + await server.run(partial(WebsocketEventHandler, args, queue)) + finally: + queue.put_nowait(None) + + +# ----------------------------------------------------------------------------- + + +async def websocket_connected(queue: "asyncio.Queue[Optional[Event]]", websocket): + try: + while True: + event = await queue.get() + if event is None: + # Stop signal + break + + await websocket.send( + json.dumps( + {"type": event.type, "data": event.data or {}}, ensure_ascii=False + ) + ) + except websockets.ConnectionClosed: + pass + except Exception: + _LOGGER.exception("Error in websocket handler") + + +# ----------------------------------------------------------------------------- + + +class WebsocketEventHandler(AsyncEventHandler): + """Event handler for clients.""" + + def __init__( + self, + cli_args: argparse.Namespace, + queue: "asyncio.Queue[Optional[Event]]", + *args, + **kwargs, + ) -> None: + super().__init__(*args, **kwargs) + + self.cli_args = cli_args + self.queue = queue + + async def handle_event(self, event: Event) -> bool: + _LOGGER.debug(event) + self.queue.put_nowait(event) + + return True + + +# ----------------------------------------------------------------------------- + + +if __name__ == "__main__": + try: + asyncio.run(main()) + except KeyboardInterrupt: + pass diff --git a/examples/websocket-service/timer_finished.wav b/examples/websocket-service/timer_finished.wav new file mode 100644 index 0000000..40a97bc Binary files /dev/null and b/examples/websocket-service/timer_finished.wav differ diff --git a/examples/websocket-service/timers.html b/examples/websocket-service/timers.html new file mode 100644 index 0000000..0c6777b --- /dev/null +++ b/examples/websocket-service/timers.html @@ -0,0 +1,244 @@ + + + + + + + + + + + Wyoming Timer Example + + + + +
+
+ + + + + diff --git a/wyoming_satellite/satellite.py b/wyoming_satellite/satellite.py index 6d5fa47..c798167 100644 --- a/wyoming_satellite/satellite.py +++ b/wyoming_satellite/satellite.py @@ -243,6 +243,8 @@ class SatelliteBase: async def event_from_server(self, event: Event) -> None: """Called when an event is received from the server.""" + forward_event = True + if Ping.is_type(event.type): # Respond with pong ping = Ping.from_event(event) @@ -252,12 +254,16 @@ class SatelliteBase: # Enable pinging self._enable_ping() _LOGGER.debug("Ping enabled") + + forward_event = False elif Pong.is_type(event.type): # Response from our ping self._pong_received_event.set() + forward_event = False elif AudioChunk.is_type(event.type): # TTS audio await self.event_to_snd(event) + forward_event = False elif AudioStart.is_type(event.type): # TTS started await self.event_to_snd(event) @@ -303,8 +309,8 @@ class SatelliteBase: _LOGGER.debug(event) await self.trigger_timer_finished(TimerFinished.from_event(event)) - # Forward everything except audio to event service - if not AudioChunk.is_type(event.type): + # Forward everything except audio/ping/pong to event service + if forward_event: await self.forward_event(event) async def _send_run_pipeline(self, pipeline_name: Optional[str] = None) -> None: