feat(web-console): rebuild intelligent cockpit sync interface
This commit is contained in:
@@ -12,7 +12,6 @@ from typing import Any
|
||||
from . import protocol
|
||||
from .mqtt_client import MqttSeatPoseClient
|
||||
from .transports import PythonCanTransport, SimulationTransport, ZlgBridgeTransport
|
||||
from .video import VideoBridge
|
||||
|
||||
AXES = (
|
||||
{"id": 0, "name": "前后", "title": "前后电机", "unit": "cm", "physical_range": 21.5, "nominal": 480},
|
||||
@@ -33,8 +32,14 @@ DEFAULT_SETTINGS: dict[str, Any] = {
|
||||
"dll_path": r"C:\Program Files (x86)\ZCANPRO\zlgcan.dll",
|
||||
"resistance": 1,
|
||||
},
|
||||
"mqtt": {"host": "172.20.10.12", "port": 1883, "topic": "zed/seatpose"},
|
||||
"video": {"host": "172.20.10.12", "port": 8554, "path": "zed"},
|
||||
"mqtt": {"host": "172.20.10.6", "port": 1883, "topic": "zed/seatpose"},
|
||||
"video": {
|
||||
"scheme": "http",
|
||||
"host": "172.20.10.6",
|
||||
"port": 8889,
|
||||
"path": "zed",
|
||||
"transport": "webrtc",
|
||||
},
|
||||
}
|
||||
|
||||
|
||||
@@ -60,7 +65,6 @@ class CalibrationService:
|
||||
self._mqtt: MqttSeatPoseClient | None = None
|
||||
self._saved_job = -1
|
||||
self.settings = self._load_settings()
|
||||
self.video = VideoBridge(self._on_video_state)
|
||||
self.state: dict[str, Any] = self._initial_state()
|
||||
|
||||
def start(self, *, auto_connect: bool = False) -> None:
|
||||
@@ -84,7 +88,6 @@ class CalibrationService:
|
||||
if self._mqtt:
|
||||
self._mqtt.close()
|
||||
self._mqtt = None
|
||||
self.video.stop()
|
||||
if self._transport:
|
||||
self._transport.close()
|
||||
self._transport = None
|
||||
@@ -103,7 +106,6 @@ class CalibrationService:
|
||||
mode = str(self.state["transport"].get("mode", "hardware"))
|
||||
if mode != "simulation":
|
||||
raise ApiError("真实台架会话不能通过虚拟重启接口复位", 409)
|
||||
self.video.stop()
|
||||
if self._mqtt:
|
||||
self._mqtt.close()
|
||||
self._mqtt = None
|
||||
@@ -285,12 +287,24 @@ class CalibrationService:
|
||||
mqtt = self.settings["mqtt"]
|
||||
video = self.settings["video"]
|
||||
self._validate_host_port(mqtt.get("host"), mqtt.get("port"), "MQTT")
|
||||
self._validate_host_port(video.get("host"), video.get("port"), "RTSP")
|
||||
self._validate_host_port(video.get("host"), video.get("port"), "WebRTC")
|
||||
scheme = str(video.get("scheme", "http")).strip().lower()
|
||||
if scheme not in {"http", "https"}:
|
||||
raise ApiError("WebRTC 信令协议必须是 http 或 https")
|
||||
path = str(video.get("path", "")).strip().strip("/")
|
||||
if not path or any(char in path for char in " ?#\\"):
|
||||
raise ApiError("RTSP 流路径无效")
|
||||
raise ApiError("WebRTC 流路径无效")
|
||||
video["scheme"] = scheme
|
||||
video["path"] = path
|
||||
video["transport"] = "webrtc"
|
||||
self.state["settings"] = json.loads(json.dumps(self.settings))
|
||||
self.state["video"].update(
|
||||
{
|
||||
"transport": "webrtc",
|
||||
"url": self._video_url(),
|
||||
"detail": "WebRTC 由浏览器直接接收",
|
||||
}
|
||||
)
|
||||
self._save_settings()
|
||||
self._touch()
|
||||
return self.snapshot()
|
||||
@@ -331,19 +345,19 @@ class CalibrationService:
|
||||
return self.snapshot()
|
||||
|
||||
def start_video(self) -> dict[str, Any]:
|
||||
config = self.settings["video"]
|
||||
uri = f"rtsp://{config['host']}:{int(config['port'])}/{str(config['path']).strip('/')}"
|
||||
try:
|
||||
self.video.start(uri)
|
||||
except Exception as error:
|
||||
self._on_video_state("error", str(error))
|
||||
raise ApiError(str(error), 503) from error
|
||||
self._on_video_state("ready", f"浏览器可直接连接 {self._video_url()}")
|
||||
return self.snapshot()
|
||||
|
||||
def stop_video(self) -> dict[str, Any]:
|
||||
self.video.stop()
|
||||
self._on_video_state("idle", "浏览器 WebRTC 接收已停止")
|
||||
return self.snapshot()
|
||||
|
||||
def _video_url(self) -> str:
|
||||
config = self.settings["video"]
|
||||
scheme = str(config.get("scheme", "http")).strip().lower()
|
||||
path = str(config["path"]).strip("/")
|
||||
return f"{scheme}://{config['host']}:{int(config['port'])}/{path}/"
|
||||
|
||||
def _initial_state(self) -> dict[str, Any]:
|
||||
return {
|
||||
"version": 0,
|
||||
@@ -399,9 +413,10 @@ class CalibrationService:
|
||||
"areas": {},
|
||||
},
|
||||
"video": {
|
||||
"state": "idle",
|
||||
"detail": "视频接收尚未启动",
|
||||
"ffmpeg_available": self.video.available,
|
||||
"state": "ready",
|
||||
"detail": "WebRTC 由浏览器直接接收",
|
||||
"transport": "webrtc",
|
||||
"url": self._video_url(),
|
||||
},
|
||||
"settings": json.loads(json.dumps(self.settings)),
|
||||
"last_result": self._load_result(),
|
||||
@@ -704,6 +719,23 @@ class CalibrationService:
|
||||
for section in settings:
|
||||
if isinstance(stored.get(section), dict):
|
||||
settings[section].update(stored[section])
|
||||
stored_video = stored.get("video")
|
||||
legacy_video_endpoint = (
|
||||
isinstance(stored_video, dict)
|
||||
and str(stored_video.get("host", "")).strip() == "172.20.10.12"
|
||||
and str(stored_video.get("port", "")).strip() == "8554"
|
||||
)
|
||||
if isinstance(stored_video, dict) and (
|
||||
"scheme" not in stored_video or legacy_video_endpoint
|
||||
):
|
||||
# Settings written by the retired RTSP/FFmpeg bridge are not
|
||||
# valid WHEP endpoints. Migrate that schema to the known ZED
|
||||
# MediaMTX endpoint instead of trying port 8554 as WebRTC. The
|
||||
# explicit endpoint check also catches legacy settings that a
|
||||
# newer UI already decorated with scheme/transport fields.
|
||||
settings["video"] = json.loads(json.dumps(DEFAULT_SETTINGS["video"]))
|
||||
if settings["mqtt"].get("host") == "172.20.10.12":
|
||||
settings["mqtt"]["host"] = DEFAULT_SETTINGS["mqtt"]["host"]
|
||||
except (OSError, ValueError):
|
||||
pass
|
||||
return settings
|
||||
|
||||
Reference in New Issue
Block a user