from __future__ import annotations from fastapi import APIRouter, HTTPException, Request from fastapi.responses import StreamingResponse from pydantic import BaseModel from starlette.concurrency import run_in_threadpool import camera_params import config router = APIRouter(prefix="/api/camera", tags=["camera"]) class ParamValuesIn(BaseModel): exposure_auto: bool exposure_auto_target_brightness: int exposure_auto_min: float exposure_auto_max: float exposure_time: int gain: float def _get_spec(request: Request, cam_id: str) -> config.CameraSpec: session = request.app.state.session specs = {c.id: c for c in config.camera_specs(session.camera_count)} spec = specs.get(cam_id) if spec is None: raise HTTPException(404, f"unknown camera id '{cam_id}' for camera_count={session.camera_count}") return spec @router.get("/{cam_id}/params") async def get_params(cam_id: str, request: Request): spec = _get_spec(request, cam_id) session = request.app.state.session values = session.camera_values.get(cam_id) or camera_params.load_defaults(spec) session.camera_values[cam_id] = values return values.as_dict() @router.post("/{cam_id}/params") async def set_params(cam_id: str, body: ParamValuesIn, request: Request): spec = _get_spec(request, cam_id) session = request.app.state.session values = camera_params.CameraParamValues(**body.model_dump()) session.camera_values[cam_id] = values return {"ok": True} @router.post("/{cam_id}/params/apply") async def apply_params(cam_id: str, request: Request): spec = _get_spec(request, cam_id) session = request.app.state.session pm = request.app.state.pm if not pm.is_running("camera"): raise HTTPException(409, "camera is not running") values = session.camera_values.get(cam_id) or camera_params.load_defaults(spec) results = await run_in_threadpool(camera_params.apply_params, spec, values) ok = all(r["ok"] for r in results) return {"ok": ok, "results": results} @router.post("/{cam_id}/params/save") async def save_params(cam_id: str, request: Request): spec = _get_spec(request, cam_id) session = request.app.state.session values = session.camera_values.get(cam_id) or camera_params.load_defaults(spec) try: await run_in_threadpool(camera_params.save_params, spec, values) except Exception as e: raise HTTPException(500, f"save failed: {e}") return {"ok": True, "path": spec.base_params_path} @router.get("/{cam_id}/stream.mjpg") async def stream(cam_id: str, request: Request): _get_spec(request, cam_id) # validate ros = request.app.state.ros return StreamingResponse( ros.mjpeg_generator(cam_id), media_type="multipart/x-mixed-replace; boundary=frame", )