""" SLMM (Sound Level Meter Manager) Proxy Router Proxies requests from SFM to the standalone SLMM backend service. SLMM runs on port 8100 and handles NL43/NL53 sound level meter communication. """ from fastapi import APIRouter, HTTPException, Request, Response from fastapi.responses import StreamingResponse import httpx import logging import os logger = logging.getLogger(__name__) router = APIRouter(prefix="/api/slmm", tags=["slmm"]) # SLMM backend URL - configurable via environment variable SLMM_BASE_URL = os.getenv("SLMM_BASE_URL", "http://localhost:8100") @router.get("/health") async def check_slmm_health(): """ Check if the SLMM backend service is reachable and healthy. """ try: async with httpx.AsyncClient(timeout=5.0) as client: response = await client.get(f"{SLMM_BASE_URL}/health") if response.status_code == 200: data = response.json() return { "status": "ok", "slmm_status": "connected", "slmm_url": SLMM_BASE_URL, "slmm_version": data.get("version", "unknown"), "slmm_response": data } else: return { "status": "degraded", "slmm_status": "error", "slmm_url": SLMM_BASE_URL, "detail": f"SLMM returned status {response.status_code}" } except httpx.ConnectError: return { "status": "error", "slmm_status": "unreachable", "slmm_url": SLMM_BASE_URL, "detail": "Cannot connect to SLMM backend. Is it running?" } except Exception as e: return { "status": "error", "slmm_status": "error", "slmm_url": SLMM_BASE_URL, "detail": str(e) } @router.api_route("/{path:path}", methods=["GET", "POST", "PUT", "DELETE", "PATCH"]) async def proxy_to_slmm(path: str, request: Request): """ Proxy all requests to the SLMM backend service. This allows SFM to act as a unified frontend for all device types, while SLMM remains a standalone backend service. """ # Build target URL target_url = f"{SLMM_BASE_URL}/api/nl43/{path}" # Get query parameters query_params = dict(request.query_params) # Get request body if present body = None if request.method in ["POST", "PUT", "PATCH"]: try: body = await request.body() except Exception as e: logger.error(f"Failed to read request body: {e}") body = None # Get headers (exclude host and other proxy-specific headers) headers = dict(request.headers) headers_to_exclude = ["host", "content-length", "transfer-encoding", "connection"] proxy_headers = {k: v for k, v in headers.items() if k.lower() not in headers_to_exclude} logger.info(f"Proxying {request.method} request to SLMM: {target_url}") try: async with httpx.AsyncClient(timeout=30.0) as client: # Forward the request to SLMM response = await client.request( method=request.method, url=target_url, params=query_params, headers=proxy_headers, content=body ) # Return the response from SLMM return Response( content=response.content, status_code=response.status_code, headers=dict(response.headers), media_type=response.headers.get("content-type") ) except httpx.ConnectError: logger.error(f"Failed to connect to SLMM backend at {SLMM_BASE_URL}") raise HTTPException( status_code=503, detail=f"SLMM backend service unavailable. Is SLMM running on {SLMM_BASE_URL}?" ) except httpx.TimeoutException: logger.error(f"Timeout connecting to SLMM backend at {SLMM_BASE_URL}") raise HTTPException( status_code=504, detail="SLMM backend timeout" ) except Exception as e: logger.error(f"Error proxying to SLMM: {e}") raise HTTPException( status_code=500, detail=f"Failed to proxy request to SLMM: {str(e)}" )