update:为 test 页面增加可配置的本地唤醒词运行时

This commit is contained in:
3030332422
2026-04-10 09:17:22 +08:00
parent c5f82369e5
commit 705b5732a6
23 changed files with 1116 additions and 3 deletions
@@ -0,0 +1,2 @@
from .app import TestRuntimeApplication
from .http_server import TestRuntimeHttpServer
@@ -0,0 +1,60 @@
import logging
from ..config import RuntimeConfig
from ..plugins import AudioPlugin, PluginManager, WakeWordPlugin
from .http_server import TestRuntimeHttpServer
logger = logging.getLogger(__name__)
class TestRuntimeApplication:
def __init__(self, config: RuntimeConfig, http_server: TestRuntimeHttpServer) -> None:
self.config = config
self.http_server = http_server
self.event_bridge = http_server.event_bridge
self.plugins = PluginManager()
self.audio_source = None
self._is_setup = False
self._is_running = False
def setup(self) -> None:
if self._is_setup:
return
if self.config.wakeword_enabled:
self.plugins.register(
AudioPlugin(),
WakeWordPlugin(),
)
self.plugins.setup_all(self)
self._is_setup = True
def start(self) -> None:
if self._is_running:
return
self.setup()
self.plugins.start_all()
if self.config.wakeword_enabled:
self.event_bridge.publish_service_ready()
self._is_running = True
logger.info("test runtime application started")
def stop(self) -> None:
if not self._is_running:
return
self.event_bridge.publish_service_stopping()
self.plugins.stop_all()
self._is_running = False
logger.info("test runtime application stopped")
def shutdown(self) -> None:
self.stop()
if self._is_setup:
self.plugins.shutdown_all()
def handle_wake_word_detected(self, wake_word: str, full_text: str) -> None:
_ = full_text
logger.info("wake word detected: %s", wake_word)
self.event_bridge.publish_detected(wake_word)
@@ -0,0 +1,102 @@
import json
import queue
from http import HTTPStatus
from http.server import SimpleHTTPRequestHandler, ThreadingHTTPServer
from pathlib import Path
from ..bridge import WakewordEventBridge
class TestRuntimeHttpServer:
def __init__(self, test_root: Path, host: str = "0.0.0.0", port: int = 8006) -> None:
self.test_root = test_root
self.host = host
self.port = port
self.event_bridge = WakewordEventBridge()
self._server = self._build_server()
@property
def page_url(self) -> str:
return f"http://127.0.0.1:{self.port}/test_page.html"
@property
def events_url(self) -> str:
return f"http://127.0.0.1:{self.port}/events"
def serve_forever(self) -> None:
self._server.serve_forever()
def shutdown(self) -> None:
self._server.shutdown()
self.event_bridge.close()
self._server.server_close()
def _build_server(self) -> ThreadingHTTPServer:
test_root = self.test_root
event_bridge = self.event_bridge
class TestRuntimeHandler(SimpleHTTPRequestHandler):
protocol_version = "HTTP/1.1"
def __init__(self, *args, **kwargs):
super().__init__(*args, directory=str(test_root), **kwargs)
def handle(self) -> None:
try:
super().handle()
except (BrokenPipeError, ConnectionResetError, ConnectionAbortedError):
pass
def do_GET(self) -> None:
if self.path == "/events":
self._handle_events(event_bridge)
return
if self.path == "/health":
body = json.dumps({"status": "ok"}).encode("utf-8")
self.send_response(HTTPStatus.OK)
self.send_header("Content-Type", "application/json; charset=utf-8")
self.send_header("Cache-Control", "no-cache")
self.send_header("Content-Length", str(len(body)))
self.end_headers()
self.wfile.write(body)
return
super().do_GET()
def log_message(self, format: str, *args) -> None:
return
def _handle_events(self, bridge: WakewordEventBridge) -> None:
client_queue = bridge.add_client()
self.send_response(HTTPStatus.OK)
self.send_header("Content-Type", "text/event-stream; charset=utf-8")
self.send_header("Cache-Control", "no-cache")
self.send_header("Connection", "keep-alive")
self.send_header("X-Accel-Buffering", "no")
self.end_headers()
try:
ready_message = bridge.build_ready_message()
self.wfile.write(f"data: {ready_message}\n\n".encode("utf-8"))
self.wfile.flush()
while bridge.is_running:
try:
message = client_queue.get(timeout=15)
if message == "__bridge_closed__":
break
self.wfile.write(f"data: {message}\n\n".encode("utf-8"))
except queue.Empty:
if not bridge.is_running:
break
self.wfile.write(b": keepalive\n\n")
self.wfile.flush()
except (BrokenPipeError, ConnectionResetError, ConnectionAbortedError):
pass
finally:
bridge.remove_client(client_queue)
server = ThreadingHTTPServer((self.host, self.port), TestRuntimeHandler)
server.daemon_threads = True
return server