camera.py 2.9 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394
  1. from datetime import datetime
  2. from fastapi import APIRouter, HTTPException, Query, Request, Response
  3. from app.core.config import settings
  4. from app.services import (
  5. CameraCaptureError,
  6. CameraUnavailableError,
  7. MqttCaptureBusyError,
  8. MqttCaptureError,
  9. get_camera_service,
  10. get_mqtt_capture_service,
  11. )
  12. router = APIRouter(
  13. prefix="/camera",
  14. tags=["camera"],
  15. )
  16. @router.api_route("/capture", methods=["GET"], summary="拍照并直接返回图片")
  17. def capture_image(
  18. download: bool = Query(
  19. default=False,
  20. description="是否以附件下载方式返回图片。默认 False,浏览器会直接预览。",
  21. ),
  22. ) -> Response:
  23. """
  24. 拍照接口。
  25. 调用这个接口时,服务会即时触发树莓派相机拍照,
  26. 然后把最新拍到的图片直接作为 HTTP 响应返回。
  27. """
  28. camera_service = get_camera_service()
  29. try:
  30. image_bytes = camera_service.capture_jpeg()
  31. except (CameraUnavailableError, CameraCaptureError) as exc:
  32. raise HTTPException(status_code=503, detail=str(exc)) from exc
  33. filename = f"capture_{datetime.now().strftime('%Y%m%d_%H%M%S')}.jpg"
  34. disposition = "attachment" if download else "inline"
  35. return Response(
  36. content=image_bytes,
  37. media_type="image/jpeg",
  38. headers={
  39. "Content-Disposition": f'{disposition}; filename="{filename}"',
  40. "Cache-Control": "no-store",
  41. },
  42. )
  43. @router.api_route(
  44. "/capture-pair",
  45. methods=["GET", "POST"],
  46. summary="通过 MQTT 联动拍摄正反两张图片",
  47. )
  48. def capture_pair_via_mqtt(request: Request) -> dict[str, object]:
  49. """
  50. 机械臂联动拍照接口。
  51. 调用流程:
  52. 1. 本接口收到请求后,临时连接 MQTT。
  53. 2. 发送 start 指令给机械臂。
  54. 3. 收到 `id=1` / `id=2` 的拍照指令后,分别覆盖保存到静态目录。
  55. 4. 等待 `status=4` 作为流程结束信号。
  56. 5. 断开 MQTT,返回两张图片的静态访问地址。
  57. 返回 JSON 而不是直接返回图片,是因为这条流程一次会产出两张图,
  58. 更适合用 URL 的方式交给前端或其他服务继续处理。
  59. """
  60. mqtt_capture_service = get_mqtt_capture_service()
  61. try:
  62. result = mqtt_capture_service.capture_pair()
  63. except MqttCaptureBusyError as exc:
  64. raise HTTPException(status_code=409, detail=str(exc)) from exc
  65. except MqttCaptureError as exc:
  66. raise HTTPException(status_code=503, detail=str(exc)) from exc
  67. front_url = str(
  68. request.url_for("static", path=settings.static.front_relative_path.as_posix())
  69. )
  70. back_url = str(
  71. request.url_for("static", path=settings.static.back_relative_path.as_posix())
  72. )
  73. return {
  74. "request_id": result["request_id"],
  75. "status": result["status"],
  76. "front_url": front_url,
  77. "back_url": back_url,
  78. }