Initial commit: scan_web integrated recording UI
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.
This commit is contained in:
@@ -0,0 +1,74 @@
|
||||
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
|
||||
Reference in New Issue
Block a user