| """Run the loopback-only WatcheRobot Vision Debug Lab.""" |
|
|
| from __future__ import annotations |
|
|
| import asyncio |
| import os |
| import socket |
| import webbrowser |
| from pathlib import Path |
|
|
| import uvicorn |
|
|
| from service import ( |
| DaemonDeviceStatusProvider, |
| VisionDebugLabService, |
| create_web_app, |
| ) |
| from watcherobot.application import ApplicationContext |
|
|
|
|
| ROOT = Path(__file__).resolve().parent |
| HOST = "127.0.0.1" |
|
|
|
|
| async def main() -> None: |
| listener = socket.socket(socket.AF_INET, socket.SOCK_STREAM) |
| listener.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) |
| listener.bind((HOST, 0)) |
| listener.listen(128) |
| port = int(listener.getsockname()[1]) |
| url = f"http://{HOST}:{port}" |
|
|
| try: |
| async with ApplicationContext.from_environment() as app: |
| device_status_url = os.environ.get( |
| "WATCHER_APP_DEVICE_STATUS_URL", |
| "", |
| ).strip() |
| if not device_status_url: |
| raise RuntimeError( |
| "Daemon did not inject WATCHER_APP_DEVICE_STATUS_URL" |
| ) |
| service = VisionDebugLabService( |
| robot=app.robot, |
| artifacts_dir=ROOT / "artifacts", |
| device_status_provider=DaemonDeviceStatusProvider( |
| device_status_url |
| ), |
| ) |
| web_app = create_web_app(service, web_root=ROOT / "web") |
| server = uvicorn.Server( |
| uvicorn.Config( |
| web_app, |
| host=HOST, |
| port=port, |
| access_log=False, |
| log_level="warning", |
| ) |
| ) |
| server_task = asyncio.create_task( |
| server.serve(sockets=[listener]), |
| name="vision-debug-lab-http", |
| ) |
| while not server.started: |
| if server_task.done(): |
| await server_task |
| await asyncio.sleep(0.02) |
| app.logger.info("Vision Debug Lab: %s", url) |
| if os.environ.get("WATCHER_VISION_LAB_NO_BROWSER") != "1": |
| await asyncio.to_thread(webbrowser.open, url) |
| await server_task |
| finally: |
| listener.close() |
|
|
|
|
| if __name__ == "__main__": |
| try: |
| asyncio.run(main()) |
| except KeyboardInterrupt: |
| pass |
|
|