Files
radar_system/python_app/webui/server.py
T
2026-06-06 00:52:52 +03:00

59 lines
2.3 KiB
Python

"""Run the web UI's FastAPI app on a background thread.
The radar app already owns the hardware and the Qt event loop, so the web server
lives in a daemon thread inside that process (uvicorn brings its own asyncio loop
for the thread). Control requests hop back to the Qt main thread via the bridge's
queued signals — the web thread never touches Qt directly.
"""
from __future__ import annotations
import logging
import threading
import uvicorn
from python_app.webui.app import create_app
from python_app.webui.controller import WebController
logger = logging.getLogger(__name__)
class WebUiServer:
"""Owns a uvicorn server bound to a controller, run on a daemon thread."""
def __init__(self, controller: WebController, *, host: str = "0.0.0.0", port: int = 8080) -> None:
"""Build the server for ``controller`` (not started until :meth:`start`)."""
config = uvicorn.Config(create_app(controller), host=host, port=port, log_level="warning")
self._server = uvicorn.Server(config)
self._thread = threading.Thread(target=self._serve, name="radar-webui", daemon=True)
def start(self) -> None:
"""Start serving on the background thread."""
config = self._server.config
logger.info("Starting web UI server on %s:%s", config.host, config.port)
self._thread.start()
def is_alive(self) -> bool:
"""Return whether the server thread is still running."""
return self._thread.is_alive()
def _serve(self) -> None:
"""Run uvicorn, surfacing a startup/runtime failure instead of dying silently.
The bind happens on this thread after ``start()`` has already returned, so a
failure (e.g. the port is taken) would otherwise be invisible.
"""
try:
self._server.run()
except Exception: # noqa: BLE001 - log, never crash the host process
logger.exception("Web UI server thread exited with an error")
def stop(self) -> None:
"""Ask uvicorn to exit and wait briefly for the thread to unwind."""
logger.info("Stopping web UI server")
self._server.should_exit = True
self._thread.join(timeout=5.0)
if self._thread.is_alive():
logger.warning("Web UI server thread did not stop within timeout")