update
This commit is contained in:
@@ -96,6 +96,31 @@ class CalibrationService:
|
||||
with self._lock:
|
||||
return json.loads(json.dumps(self.state, ensure_ascii=False))
|
||||
|
||||
def reset_virtual_session(self) -> dict[str, Any]:
|
||||
"""Return a simulation run to its safe initial state without touching hardware."""
|
||||
|
||||
with self._lock:
|
||||
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
|
||||
if self._transport:
|
||||
self._transport.close()
|
||||
self._transport = None
|
||||
with self._lock:
|
||||
self._pending.clear()
|
||||
self._saved_job = -1
|
||||
self.state = self._initial_state()
|
||||
self.state["transport"].update(
|
||||
{"mode": "simulation", "state": "disconnected", "detail": "虚拟运行已恢复初始状态"}
|
||||
)
|
||||
self.state["operation"]["label"] = "等待连接标定仿真台架"
|
||||
self._touch()
|
||||
return self.snapshot()
|
||||
|
||||
def wait_for_version(self, after: int, timeout: float = 15.0) -> dict[str, Any]:
|
||||
deadline = time.monotonic() + timeout
|
||||
while not self._closing.is_set() and time.monotonic() < deadline:
|
||||
|
||||
@@ -363,22 +363,33 @@ class ModeProfile:
|
||||
|
||||
|
||||
class ModeProfileRegistry:
|
||||
"""Load, match, and persist long-term cabin mode profiles."""
|
||||
"""Load preset profiles and optionally keep user profiles in a session overlay."""
|
||||
|
||||
def __init__(self, profile_root: Path):
|
||||
def __init__(self, profile_root: Path, user_root: Path | None = None):
|
||||
self.profile_root = Path(profile_root)
|
||||
self.user_root = Path(user_root) if user_root is not None else self.profile_root
|
||||
self.session_user_profiles = user_root is not None
|
||||
self._profiles: list[ModeProfile] = []
|
||||
self.reload()
|
||||
|
||||
def reload(self) -> None:
|
||||
if not self.profile_root.exists():
|
||||
self._profiles = []
|
||||
return
|
||||
self._profiles = [
|
||||
ModeProfile.from_yaml(path, self.profile_root)
|
||||
for path in sorted(self.profile_root.rglob("*.yaml"))
|
||||
if path.is_file()
|
||||
]
|
||||
profiles: list[ModeProfile] = []
|
||||
if self.profile_root.exists():
|
||||
for path in sorted(self.profile_root.rglob("*.yaml")):
|
||||
if not path.is_file():
|
||||
continue
|
||||
if self.session_user_profiles and _is_user_profile_path(
|
||||
path, self.profile_root
|
||||
):
|
||||
continue
|
||||
profiles.append(ModeProfile.from_yaml(path, self.profile_root))
|
||||
if self.session_user_profiles and self.user_root.exists():
|
||||
profiles.extend(
|
||||
ModeProfile.from_yaml(path, self.user_root)
|
||||
for path in sorted(self.user_root.rglob("*.yaml"))
|
||||
if path.is_file()
|
||||
)
|
||||
self._profiles = profiles
|
||||
|
||||
def profiles(self) -> list[ModeProfile]:
|
||||
return list(self._profiles)
|
||||
@@ -459,9 +470,9 @@ class ModeProfileRegistry:
|
||||
],
|
||||
}
|
||||
path = (
|
||||
self.profile_root / "vehicles" / vehicle_id / "user" / f"{profile_id}.yaml"
|
||||
self.user_root / "vehicles" / vehicle_id / "user" / f"{profile_id}.yaml"
|
||||
if vehicle_id
|
||||
else self.profile_root / "common" / "user" / f"{profile_id}.yaml"
|
||||
else self.user_root / "common" / "user" / f"{profile_id}.yaml"
|
||||
)
|
||||
path.parent.mkdir(parents=True, exist_ok=True)
|
||||
temporary = path.with_suffix(path.suffix + ".tmp")
|
||||
@@ -471,7 +482,7 @@ class ModeProfileRegistry:
|
||||
)
|
||||
temporary.replace(path)
|
||||
self.reload()
|
||||
return ModeProfile.from_yaml(path, self.profile_root)
|
||||
return ModeProfile.from_yaml(path, self.user_root)
|
||||
|
||||
def delete_user_mode(
|
||||
self,
|
||||
@@ -495,11 +506,15 @@ class ModeProfileRegistry:
|
||||
raise PermissionError("preset mode profiles cannot be deleted")
|
||||
|
||||
path = Path(profile.source).resolve()
|
||||
profile_root = self.profile_root.resolve()
|
||||
try:
|
||||
relative_parts = path.relative_to(profile_root).parts
|
||||
except ValueError as exc:
|
||||
raise PermissionError("mode profile is outside the configured registry") from exc
|
||||
relative_parts = None
|
||||
for root in {self.profile_root.resolve(), self.user_root.resolve()}:
|
||||
try:
|
||||
relative_parts = path.relative_to(root).parts
|
||||
break
|
||||
except ValueError:
|
||||
continue
|
||||
if relative_parts is None:
|
||||
raise PermissionError("mode profile is outside the configured registry")
|
||||
if "user" not in relative_parts or path.suffix.lower() != ".yaml":
|
||||
raise PermissionError("only user-defined mode profiles can be deleted")
|
||||
if not path.is_file():
|
||||
@@ -532,7 +547,7 @@ class ModeProfileRegistry:
|
||||
document = _mode_profile_document(profile, merged_operations)
|
||||
_replace_yaml(path, document)
|
||||
self.reload()
|
||||
return ModeProfile.from_yaml(path, self.profile_root)
|
||||
return ModeProfile.from_yaml(path, self._root_for_path(path))
|
||||
|
||||
def restore_initial_mode(self, profile: ModeProfile) -> ModeProfile:
|
||||
path = Path(profile.source)
|
||||
@@ -544,7 +559,17 @@ class ModeProfileRegistry:
|
||||
temporary.write_text(backup_path.read_text(encoding="utf-8"), encoding="utf-8")
|
||||
temporary.replace(path)
|
||||
self.reload()
|
||||
return ModeProfile.from_yaml(path, self.profile_root)
|
||||
return ModeProfile.from_yaml(path, self._root_for_path(path))
|
||||
|
||||
def _root_for_path(self, path: Path) -> Path:
|
||||
resolved = path.resolve()
|
||||
for root in (self.user_root.resolve(), self.profile_root.resolve()):
|
||||
try:
|
||||
resolved.relative_to(root)
|
||||
return root
|
||||
except ValueError:
|
||||
continue
|
||||
return self.profile_root
|
||||
|
||||
def _profiles_for_vehicle(self, vehicle_id: str | None) -> list[ModeProfile]:
|
||||
selected: dict[str, ModeProfile] = {}
|
||||
|
||||
@@ -6,6 +6,7 @@ import json
|
||||
import os
|
||||
import re
|
||||
import sys
|
||||
import tempfile
|
||||
import time
|
||||
import uuid
|
||||
from concurrent.futures import ThreadPoolExecutor
|
||||
@@ -237,7 +238,14 @@ class SeatDemoService:
|
||||
}
|
||||
self.model_plan_adapter = ModelPlanAdapter(self.capability_context)
|
||||
self.vehicle_profiles = VehicleProfileRegistry(repository_root / "config" / "vehicles")
|
||||
self.mode_profiles = ModeProfileRegistry(repository_root / "config" / "modes")
|
||||
self._mode_session_directory = tempfile.TemporaryDirectory(
|
||||
prefix="cognitive-agent-mode-session-",
|
||||
ignore_cleanup_errors=True,
|
||||
)
|
||||
self.mode_profiles = ModeProfileRegistry(
|
||||
repository_root / "config" / "modes",
|
||||
user_root=Path(self._mode_session_directory.name),
|
||||
)
|
||||
self._artifact_root = repository_root / "artifacts" / "runs"
|
||||
self.conversations = ConversationStateStore(
|
||||
preset_root=repository_root / "artifacts" / "user-presets"
|
||||
@@ -343,6 +351,7 @@ class SeatDemoService:
|
||||
"vehicle": default_vehicle.public_dict(),
|
||||
"vehicles": self.vehicle_profiles.public_profiles(),
|
||||
"modes": self.mode_profiles.public_modes(default_vehicle.id),
|
||||
"mode_profile_storage": "runtime_session_volatile",
|
||||
"preset_storage": "conversation_durable",
|
||||
"inferred_level_range": [1, 3],
|
||||
"motion_position_range_pct": [0, 100],
|
||||
|
||||
@@ -505,6 +505,7 @@ class SeatDemoHandler(BaseHTTPRequestHandler):
|
||||
int(payload["axis"]), bool(payload.get("far"))
|
||||
),
|
||||
"/api/calibration/stop": self.calibration.stop,
|
||||
"/api/calibration/session/reset": self.calibration.reset_virtual_session,
|
||||
"/api/calibration/settings": lambda: self.calibration.update_settings(
|
||||
dict(payload)
|
||||
),
|
||||
|
||||
Reference in New Issue
Block a user