f34817d5b6
FastAPI backend + vanilla JS frontend for LiDAR/camera startup, camera settings, and rosbag recording, unifying the previous scan_gui/scan_gui_dual/ scan_gui_triple desktop tools into one web app.
75 lines
2.5 KiB
Python
75 lines
2.5 KiB
Python
from __future__ import annotations
|
|
|
|
import asyncio
|
|
|
|
from fastapi import APIRouter, WebSocket, WebSocketDisconnect
|
|
|
|
import config
|
|
from log_tail import LogTailer
|
|
from routers.system import build_status
|
|
|
|
router = APIRouter()
|
|
|
|
STATUS_PUSH_HZ = 1.0 # slowed from 2.0 — paired with health.py's wider RATE_WINDOW,
|
|
# the operator asked for a calmer/more trustworthy number over a
|
|
# snappier one (2026-08-06).
|
|
LOG_POLL_HZ = 4.0
|
|
|
|
|
|
@router.websocket("/ws/status")
|
|
async def ws_status(ws: WebSocket):
|
|
await ws.accept()
|
|
period = 1.0 / STATUS_PUSH_HZ
|
|
try:
|
|
while True:
|
|
await ws.send_json(build_status(ws.app))
|
|
await asyncio.sleep(period)
|
|
except WebSocketDisconnect:
|
|
pass
|
|
|
|
|
|
@router.websocket("/ws/pointcloud")
|
|
async def ws_pointcloud(ws: WebSocket, profile: str = "desktop"):
|
|
"""M3 live scan view — binary-framed decimated point cloud, see
|
|
pointcloud_codec.py for the wire format. `profile` picks desktop vs.
|
|
phone point/Hz budgets rather than trusting client-supplied numbers
|
|
directly (a client could otherwise ask for an unbounded frame size)."""
|
|
await ws.accept()
|
|
prof = config.POINTCLOUD_PROFILES.get(profile, config.POINTCLOUD_PROFILES["desktop"])
|
|
period = 1.0 / prof["hz"]
|
|
ros = ws.app.state.ros
|
|
try:
|
|
while True:
|
|
await ws.send_bytes(ros.pointcloud.encode_frame(max_points=prof["max_points"]))
|
|
await asyncio.sleep(period)
|
|
except WebSocketDisconnect:
|
|
pass
|
|
|
|
|
|
@router.websocket("/ws/logs")
|
|
async def ws_logs(ws: WebSocket):
|
|
"""Unified log tail — mirrors the old PyQt GUIs' single scrollback pane.
|
|
Sends recent backlog once on connect, then only newly-appended lines."""
|
|
await ws.accept()
|
|
tailer = LogTailer({
|
|
"lidar": config.LIDAR_LOG_PATH,
|
|
"camera": config.CAMERA_LOG_PATH,
|
|
"recording": config.RECORDING_LOG_PATH,
|
|
"fastlio": config.FASTLIO_LOG_PATH,
|
|
})
|
|
backlog = []
|
|
for tag in ("lidar", "camera", "recording", "fastlio"):
|
|
backlog += [{"tag": tag, "line": ln} for ln in tailer.read_tail(tag, max_lines=80)]
|
|
if backlog:
|
|
await ws.send_json({"backlog": backlog})
|
|
|
|
period = 1.0 / LOG_POLL_HZ
|
|
try:
|
|
while True:
|
|
new_lines = [{"tag": tag, "line": line} for tag, line in tailer.read_new()]
|
|
if new_lines:
|
|
await ws.send_json({"lines": new_lines})
|
|
await asyncio.sleep(period)
|
|
except WebSocketDisconnect:
|
|
pass
|