Organize host and robot streaming releases

This commit is contained in:
Mike Mi
2026-08-09 15:56:20 +08:00
commit decff19daf
532 changed files with 127054 additions and 0 deletions

View File

@@ -0,0 +1,123 @@
from __future__ import annotations
import json
import time
from django.views.decorators.csrf import csrf_exempt
from django.http import HttpResponse, StreamingHttpResponse
from rest_framework.decorators import api_view
from rest_framework.response import Response
from .services import camera_control_service, gps_service, network_service, video_service
@api_view(["GET"])
def dashboard_snapshot(request):
return Response(
{
"gps": gps_service.get_latest(),
"network": network_service.get_latest(),
"video": video_service.get_status(),
}
)
@api_view(["GET"])
def gps_latest(request):
return Response(gps_service.get_latest())
@api_view(["GET"])
def network_latest(request):
return Response(network_service.get_latest())
@api_view(["GET"])
def clock_calibration(request):
server_received_unix_ns = time.time_ns()
server_sent_unix_ns = time.time_ns()
response = Response(
{
"server_received_unix_ms": round(server_received_unix_ns / 1_000_000.0, 3),
"server_sent_unix_ms": round(server_sent_unix_ns / 1_000_000.0, 3),
}
)
response["Cache-Control"] = "no-store, no-cache, must-revalidate, max-age=0"
return response
@api_view(["GET"])
def video_status(request):
return Response(video_service.get_status())
@csrf_exempt
@api_view(["GET", "POST"])
def video_camera(request):
if request.method == "GET":
return Response(camera_control_service.get_camera_status())
camera = str(request.data.get("camera") or "").strip().lower()
if camera not in {"head", "waist"}:
return Response({"detail": "camera must be 'head' or 'waist'"}, status=400)
try:
result = camera_control_service.select_camera(camera)
except (OSError, RuntimeError) as error:
return Response({"detail": str(error)}, status=503)
return Response(result, status=200 if result.get("confirmed") else 504)
def video_frame(request):
try:
frame, headers = video_service.get_next_frame_with_headers()
except (FileNotFoundError, RuntimeError) as error:
status = video_service.get_status()
return HttpResponse(
status.get("source_detail") or str(error),
status=503,
content_type="text/plain; charset=utf-8",
)
response = HttpResponse(frame, content_type="image/jpeg")
response["Cache-Control"] = "no-store, no-cache, must-revalidate, max-age=0"
for key, value in headers.items():
response[key] = value
return response
def video_stream(request):
status = video_service.get_status()
if not status["available"]:
return HttpResponse(
status.get("source_detail") or f"JPEG frame directory not found: {status['frame_dir']}",
status=503,
content_type="text/plain; charset=utf-8",
)
try:
fps = float(request.GET.get("fps", status["fps"]))
except (TypeError, ValueError):
fps = float(status["fps"])
response = StreamingHttpResponse(
video_service.iter_mjpeg(fps=fps),
content_type="multipart/x-mixed-replace; boundary=frame",
)
response["Cache-Control"] = "no-store, no-cache, must-revalidate, max-age=0"
return response
@csrf_exempt
@api_view(["POST"])
def video_display_probe(request):
try:
payload = json.loads(request.body.decode("utf-8"))
except (UnicodeDecodeError, json.JSONDecodeError):
return Response({"detail": "invalid json"}, status=400)
if not isinstance(payload, dict):
return Response({"detail": "expected json object"}, status=400)
video_service.record_display_probe(payload)
return Response({"ok": True})