feat: harden seat intent and expand HIL coverage
This commit is contained in:
@@ -3,6 +3,7 @@
|
||||
from __future__ import annotations
|
||||
|
||||
import json
|
||||
import os
|
||||
import re
|
||||
import sys
|
||||
import time
|
||||
@@ -22,9 +23,11 @@ if str(SEAT_SOURCE) not in sys.path:
|
||||
|
||||
from seat_domain import ( # noqa: E402
|
||||
HardwareSeatExecutor,
|
||||
SIMULATED_HARDWARE_FAULTS,
|
||||
SeatCanMatrix,
|
||||
SeatCompiler,
|
||||
T2GSeatCompiler,
|
||||
SimulatedHardwareSeatExecutor,
|
||||
VirtualSeatExecutor,
|
||||
build_function_config,
|
||||
build_function_preset,
|
||||
@@ -120,6 +123,10 @@ class SeatDemoService:
|
||||
)
|
||||
self.compiler = SeatCompiler(self.domain_root / "can" / "signal-mapping.yaml")
|
||||
self.executor = VirtualSeatExecutor(self.matrix)
|
||||
self.simulated_hardware_executor = SimulatedHardwareSeatExecutor(self.matrix)
|
||||
self.simulated_hardware_enabled = os.getenv(
|
||||
"COGNITIVE_AGENT_SIMULATED_HARDWARE_ENABLED", "false"
|
||||
).strip().lower() in {"1", "true", "yes", "on"}
|
||||
self.hardware_matrix = SeatCanMatrix(
|
||||
[
|
||||
self.domain_root / "can" / "seat-thermal-hardware-matrix.yaml",
|
||||
@@ -131,6 +138,10 @@ class SeatDemoService:
|
||||
repository_root / "platforms" / "t2g_seat_bench" / "calibration" / "seat.yaml",
|
||||
)
|
||||
self.hardware_settings = SeatHardwareSettings.from_env(repository_root)
|
||||
# Shared by the short-lived hardware executor instances. A process must
|
||||
# establish a fresh physical four-axis reference before trusting stored
|
||||
# Hall counts, and a firmware replacement invalidates it again.
|
||||
self._seat_position_memory_state = {"synchronized": False}
|
||||
self.openai = OpenAIIntentInterpreter()
|
||||
self.domain_runtimes = {
|
||||
domain: ConfiguredDomainRuntime(repository_root, domain)
|
||||
@@ -171,11 +182,21 @@ class SeatDemoService:
|
||||
"domain": "cabin",
|
||||
"platform": "reference_vehicle",
|
||||
"execution_mode": "dry_run",
|
||||
"execution_modes": ["dry_run", "hardware"],
|
||||
"execution_modes": ["dry_run", "simulated_hardware", "hardware"],
|
||||
"hardware_execution_allowed": self.hardware_settings.configured,
|
||||
"hardware": self.hardware_settings.public_info(),
|
||||
"seat_position_memory_synchronized": bool(
|
||||
self._seat_position_memory_state.get("synchronized", False)
|
||||
),
|
||||
"simulated_hardware": {
|
||||
"enabled": self.simulated_hardware_enabled,
|
||||
"default_fault": "normal",
|
||||
"fault_scenarios": list(SIMULATED_HARDWARE_FAULTS),
|
||||
"physical_can_opened": False,
|
||||
},
|
||||
"openai_available": self.openai.available,
|
||||
"openai_model": self.openai.model,
|
||||
"openai_responses_endpoint": self.openai.responses_endpoint,
|
||||
"conversation_memory": True,
|
||||
"default_vehicle_id": default_vehicle.id,
|
||||
"vehicle": default_vehicle.public_dict(),
|
||||
@@ -272,6 +293,10 @@ class SeatDemoService:
|
||||
auto_home=self.hardware_settings.auto_home_enabled,
|
||||
home_timeout_s=self.hardware_settings.home_timeout_s,
|
||||
firmware_guard=self.hardware_settings.firmware_guard(),
|
||||
synchronize_position_before_first_motion=(
|
||||
self.hardware_settings.position_sync_enabled
|
||||
),
|
||||
position_sync_state=self._seat_position_memory_state,
|
||||
)
|
||||
|
||||
def process(
|
||||
@@ -282,6 +307,7 @@ class SeatDemoService:
|
||||
execution_mode: str = "dry_run",
|
||||
approval_token: str | None = None,
|
||||
vehicle_id: str | None = None,
|
||||
simulation_fault: str = "normal",
|
||||
) -> dict[str, Any]:
|
||||
utterance = utterance.strip()
|
||||
if not utterance:
|
||||
@@ -290,8 +316,21 @@ class SeatDemoService:
|
||||
raise ValueError("utterance must be at most 1000 characters")
|
||||
if provider not in {"auto", "openai", "local"}:
|
||||
raise ValueError("provider must be auto, openai, or local")
|
||||
if execution_mode not in {"dry_run", "hardware"}:
|
||||
raise ValueError("execution_mode must be dry_run or hardware")
|
||||
if execution_mode not in {"dry_run", "simulated_hardware", "hardware"}:
|
||||
raise ValueError(
|
||||
"execution_mode must be dry_run, simulated_hardware, or hardware"
|
||||
)
|
||||
simulated_hardware_request = execution_mode == "simulated_hardware"
|
||||
if simulated_hardware_request and not self.simulated_hardware_enabled:
|
||||
raise HardwareSafetyError(
|
||||
"simulated hardware is disabled; set "
|
||||
"COGNITIVE_AGENT_SIMULATED_HARDWARE_ENABLED=true"
|
||||
)
|
||||
if simulation_fault not in SIMULATED_HARDWARE_FAULTS:
|
||||
raise ValueError(
|
||||
"simulation_fault must be one of "
|
||||
+ ", ".join(SIMULATED_HARDWARE_FAULTS)
|
||||
)
|
||||
mode_save_name = self._parse_mode_profile_save_name(utterance)
|
||||
mode_restore_name = self._parse_mode_profile_restore_name(utterance)
|
||||
mode_update = self._parse_mode_profile_update(utterance)
|
||||
@@ -329,6 +368,8 @@ class SeatDemoService:
|
||||
"hardware_execution_allowed": (
|
||||
hardware_request and not control_plane_request
|
||||
),
|
||||
"simulated_hardware": simulated_hardware_request,
|
||||
"simulation_fault": simulation_fault if simulated_hardware_request else None,
|
||||
"vehicle": vehicle_profile.public_dict(),
|
||||
"matched_modes": [
|
||||
profile.public_dict() for profile in matched_modes
|
||||
@@ -483,6 +524,8 @@ class SeatDemoService:
|
||||
result,
|
||||
traces,
|
||||
hardware_request=hardware_request,
|
||||
simulated_hardware_request=simulated_hardware_request,
|
||||
simulation_fault=simulation_fault,
|
||||
vehicle_profile=vehicle_profile,
|
||||
)
|
||||
|
||||
@@ -763,11 +806,19 @@ class SeatDemoService:
|
||||
)
|
||||
+ "板端 Position_Valid 将在发送绝对移动命令前实时检查。"
|
||||
if hardware_request
|
||||
else "平台、矩阵和策略均限制为 dry-run;硬件发送路径未启用。"
|
||||
else (
|
||||
"模拟台架隔离门禁已通过;仅生成故障注入反馈,物理 CAN、ZLG 与 IAR 路径均未打开。"
|
||||
if simulated_hardware_request
|
||||
else "平台、矩阵和策略均限制为 dry-run;硬件发送路径未启用。"
|
||||
)
|
||||
),
|
||||
safety_started,
|
||||
{
|
||||
"hardware_execution_allowed": hardware_request,
|
||||
"simulated_hardware": simulated_hardware_request,
|
||||
"simulation_fault": (
|
||||
simulation_fault if simulated_hardware_request else None
|
||||
),
|
||||
"position_calibration_gate": (
|
||||
"runtime_can_preflight" if hardware_request else "not_applicable"
|
||||
),
|
||||
@@ -788,6 +839,13 @@ class SeatDemoService:
|
||||
execution_report = hardware_executor.execute(
|
||||
command_plan, function_config, context["devices"]
|
||||
)
|
||||
elif simulated_hardware_request:
|
||||
execution_report = self.simulated_hardware_executor.execute(
|
||||
command_plan,
|
||||
function_config,
|
||||
context["devices"],
|
||||
fault_scenario=simulation_fault,
|
||||
)
|
||||
else:
|
||||
execution_report = self.executor.execute(
|
||||
command_plan, function_config, context["devices"]
|
||||
@@ -845,7 +903,15 @@ class SeatDemoService:
|
||||
else hardware_failure_summary
|
||||
)
|
||||
if hardware_request
|
||||
else "虚拟 CAN 已编码命令帧并生成模拟回读。"
|
||||
else (
|
||||
"模拟台架已生成隔离的 HIL 反馈,未访问物理 CAN。"
|
||||
if simulated_hardware_request and execution_succeeded
|
||||
else (
|
||||
f"模拟台架故障已触发:{failed_execution['message']}"
|
||||
if simulated_hardware_request and failed_execution
|
||||
else "虚拟 CAN 已编码命令帧并生成模拟回读。"
|
||||
)
|
||||
)
|
||||
)
|
||||
),
|
||||
execute_started,
|
||||
@@ -861,6 +927,14 @@ class SeatDemoService:
|
||||
},
|
||||
)
|
||||
if execution_succeeded:
|
||||
execution_notices = [
|
||||
str(item.get("telemetry", {}).get("user_notice"))
|
||||
for item in execution_results
|
||||
if item.get("telemetry", {}).get("user_notice")
|
||||
]
|
||||
for notice in execution_notices:
|
||||
if notice not in parsed["decision_summary"]:
|
||||
parsed["decision_summary"].append(notice)
|
||||
state_actions = (
|
||||
self._hardware_feedback_actions(
|
||||
parsed["actions"], function_config, execution_report
|
||||
@@ -950,7 +1024,15 @@ class SeatDemoService:
|
||||
else "未获得满足目标的真实硬件反馈。"
|
||||
)
|
||||
if hardware_request
|
||||
else "虚构反馈帧中的最终状态与请求目标一致。"
|
||||
else (
|
||||
"模拟台架反馈与请求目标一致,物理 CAN 始终关闭。"
|
||||
if simulated_hardware_request and execution_succeeded
|
||||
else (
|
||||
f"模拟台架故障反馈:{failed_execution['message']}"
|
||||
if simulated_hardware_request and failed_execution
|
||||
else "虚构反馈帧中的最终状态与请求目标一致。"
|
||||
)
|
||||
)
|
||||
)
|
||||
),
|
||||
time.perf_counter(),
|
||||
@@ -958,6 +1040,9 @@ class SeatDemoService:
|
||||
"command_frame": telemetry.get("command_frame", {}).get("data_hex"),
|
||||
"feedback_frame": telemetry.get("feedback_frame", {}).get("data_hex"),
|
||||
"simulated": not hardware_request,
|
||||
"simulation_fault": (
|
||||
simulation_fault if simulated_hardware_request else None
|
||||
),
|
||||
},
|
||||
)
|
||||
self._persist(result)
|
||||
@@ -1026,9 +1111,16 @@ class SeatDemoService:
|
||||
action["position_valid"] = bool(
|
||||
telemetry["position_valid"]
|
||||
)
|
||||
if telemetry.get("motion_mode") == "relative_timed_duty":
|
||||
if telemetry.get("motion_mode") in {
|
||||
"relative_timed_duty",
|
||||
"timed_user_request",
|
||||
}:
|
||||
action["position_estimated"] = True
|
||||
action["feedback_basis"] = "relative_hall_confirmed_open_loop"
|
||||
action["feedback_basis"] = (
|
||||
"timed_endpoint_or_hall_confirmed_open_loop"
|
||||
if telemetry.get("motion_mode") == "timed_user_request"
|
||||
else "relative_hall_confirmed_open_loop"
|
||||
)
|
||||
continue
|
||||
raw = telemetry.get("feedback_value")
|
||||
if raw is not None:
|
||||
@@ -1141,6 +1233,35 @@ class SeatDemoService:
|
||||
return parsed
|
||||
if self._contains_seat_preset_request(parsed):
|
||||
return parsed
|
||||
if len(matched_modes) > 1:
|
||||
rejected = deepcopy(parsed)
|
||||
for key in (
|
||||
"actions",
|
||||
"domain_actions",
|
||||
"preset_actions",
|
||||
"preset_events",
|
||||
"deferred_requests",
|
||||
"model_requests",
|
||||
):
|
||||
rejected[key] = []
|
||||
rejected["ambiguities"] = [
|
||||
{
|
||||
"field": "mode_profile",
|
||||
"question": "一条请求只能执行一个座椅功能模式,请确认要执行的模式。",
|
||||
}
|
||||
]
|
||||
rejected["decision_summary"] = [
|
||||
"同一条请求命中多个功能模式,已停止生成控制指令以避免模式冲突。"
|
||||
]
|
||||
return rejected
|
||||
|
||||
safety_ambiguities = [
|
||||
item
|
||||
for item in parsed.get("ambiguities", [])
|
||||
if item.get("field") != "capability"
|
||||
]
|
||||
if safety_ambiguities:
|
||||
return parsed
|
||||
|
||||
mode_requests = self.mode_profiles.requests_for_profiles(matched_modes)
|
||||
detected_domains = []
|
||||
@@ -1969,6 +2090,8 @@ class SeatDemoService:
|
||||
traces: list[dict[str, Any]],
|
||||
*,
|
||||
hardware_request: bool = False,
|
||||
simulated_hardware_request: bool = False,
|
||||
simulation_fault: str = "normal",
|
||||
vehicle_profile: VehicleProfile | None = None,
|
||||
) -> dict[str, Any]:
|
||||
"""Run isolated adapters, with only the passenger seat eligible for HIL."""
|
||||
@@ -2165,11 +2288,19 @@ class SeatDemoService:
|
||||
(
|
||||
"副驾座椅已通过 T2G HIL 安全门,其他座舱领域保持虚拟 CAN。"
|
||||
if hardware_request
|
||||
else "所有领域均为 dry-run;矩阵缺少实车完整性信息,硬件发送保持禁用。"
|
||||
else (
|
||||
"模拟台架仅对座椅结果注入故障;全部领域的物理发送路径保持关闭。"
|
||||
if simulated_hardware_request
|
||||
else "所有领域均为 dry-run;矩阵缺少实车完整性信息,硬件发送保持禁用。"
|
||||
)
|
||||
),
|
||||
time.perf_counter(),
|
||||
{
|
||||
"hardware_execution_allowed": hardware_request,
|
||||
"simulated_hardware": simulated_hardware_request,
|
||||
"simulation_fault": (
|
||||
simulation_fault if simulated_hardware_request else None
|
||||
),
|
||||
"hardware_domains": ["seat"] if hardware_request else [],
|
||||
"virtual_domains": [
|
||||
domain
|
||||
@@ -2190,6 +2321,13 @@ class SeatDemoService:
|
||||
reports[domain] = self._new_hardware_executor().execute(
|
||||
plans[domain], config, context["devices"]
|
||||
)
|
||||
elif simulated_hardware_request:
|
||||
reports[domain] = self.simulated_hardware_executor.execute(
|
||||
plans[domain],
|
||||
config,
|
||||
context["devices"],
|
||||
fault_scenario=simulation_fault,
|
||||
)
|
||||
else:
|
||||
reports[domain] = self.executor.execute(
|
||||
plans[domain], config, context["devices"]
|
||||
@@ -2212,8 +2350,9 @@ class SeatDemoService:
|
||||
if hardware_request
|
||||
else None
|
||||
)
|
||||
simulated_seat = simulated_hardware_request and "seat" in reports
|
||||
report_spec: dict[str, Any] = {
|
||||
"mode": "hil" if hardware_request else "dry_run",
|
||||
"mode": "hil" if hardware_request or simulated_seat else "dry_run",
|
||||
"status": "succeeded" if execution_succeeded else "failed",
|
||||
"started_at": execution_started_at,
|
||||
"finished_at": _now(),
|
||||
@@ -2229,14 +2368,27 @@ class SeatDemoService:
|
||||
"name": (
|
||||
"Cabin orchestration mixed HIL execution report"
|
||||
if hardware_request
|
||||
else "Cabin orchestration virtual execution report"
|
||||
else (
|
||||
"Cabin orchestration simulated hardware execution report"
|
||||
if simulated_seat
|
||||
else "Cabin orchestration virtual execution report"
|
||||
)
|
||||
),
|
||||
"version": "1.0.0",
|
||||
"request_id": request_id,
|
||||
"labels": {
|
||||
"simulation": "mixed" if hardware_request else "true",
|
||||
"seat_feedback": (
|
||||
"physical_can" if hardware_request else "simulated"
|
||||
"physical_can"
|
||||
if hardware_request
|
||||
else (
|
||||
"simulated_hardware"
|
||||
if simulated_seat
|
||||
else "simulated"
|
||||
)
|
||||
),
|
||||
"fault_scenario": (
|
||||
simulation_fault if simulated_seat else "none"
|
||||
),
|
||||
"orchestration": "cross_domain"
|
||||
if len(configs) > 1
|
||||
@@ -2272,7 +2424,20 @@ class SeatDemoService:
|
||||
else (
|
||||
f"副驾座椅真实执行失败:{failed_execution['message']}"
|
||||
if hardware_request and failed_execution
|
||||
else f"已虚拟执行 {len(report_results)} 个领域操作并汇总反馈。"
|
||||
else (
|
||||
f"模拟台架已执行 {len(report_results)} 个领域操作,物理 CAN 未打开。"
|
||||
if simulated_seat and execution_succeeded
|
||||
else (
|
||||
f"模拟台架故障已触发:{failed_execution['message']}"
|
||||
if simulated_seat and failed_execution
|
||||
else (
|
||||
"本次没有座椅动作,故障注入未应用;"
|
||||
f"已虚拟执行 {len(report_results)} 个领域操作。"
|
||||
if simulated_hardware_request
|
||||
else f"已虚拟执行 {len(report_results)} 个领域操作并汇总反馈。"
|
||||
)
|
||||
)
|
||||
)
|
||||
)
|
||||
),
|
||||
execute_started,
|
||||
|
||||
Reference in New Issue
Block a user