feat: integrate cabin CAN and verified dynamic seat modes
This commit is contained in:
@@ -8,6 +8,7 @@ import re
|
||||
import sys
|
||||
import time
|
||||
import uuid
|
||||
from concurrent.futures import ThreadPoolExecutor
|
||||
from copy import deepcopy
|
||||
from datetime import UTC, datetime
|
||||
from pathlib import Path
|
||||
@@ -46,10 +47,16 @@ from cognitive_agent.application.mode_profile import ( # noqa: E402
|
||||
from cognitive_agent.application.model_plan_adapter import ( # noqa: E402
|
||||
ModelPlanAdapter,
|
||||
APPLY_PRESET_CAPABILITY,
|
||||
DYNAMIC_MODE_STOP_CAPABILITY,
|
||||
SAVE_PRESET_CAPABILITY,
|
||||
SEAT_CAPABILITIES,
|
||||
SEAT_MOTION_CAPABILITIES,
|
||||
)
|
||||
from cognitive_agent.application.native_can_intent import merge_native_can_intent # noqa: E402
|
||||
from cognitive_agent.application.native_can_runtime import ( # noqa: E402
|
||||
NativeCanExecutor,
|
||||
NativeCanRuntime,
|
||||
)
|
||||
from cognitive_agent.application.seat_calibration import position_pct_to_angle # noqa: E402
|
||||
from cognitive_agent.application.vehicle_profile import ( # noqa: E402
|
||||
VehicleProfile,
|
||||
@@ -63,7 +70,9 @@ from cognitive_agent.infrastructure.can.hardware import ( # noqa: E402
|
||||
HardwareSafetyError,
|
||||
PHYSICAL_BENCH_COORDINATOR,
|
||||
SeatHardwareSettings,
|
||||
VehicleCanSettings,
|
||||
)
|
||||
from cognitive_agent.infrastructure.can.gateway import ZlgChannelConfig # noqa: E402
|
||||
from cognitive_agent.infrastructure.can.access import ( # noqa: E402
|
||||
PHYSICAL_BENCH_ACCESS,
|
||||
)
|
||||
@@ -112,6 +121,18 @@ SEAT_AXIS_TO_CAPABILITY = {
|
||||
SEAT_FUNCTION_TO_CAPABILITY = {
|
||||
function: capability for capability, function in SEAT_CAPABILITIES.items()
|
||||
}
|
||||
DYNAMIC_MODE_STOP_WORDS = ("停止", "关闭", "结束", "取消", "停下", "退出")
|
||||
DYNAMIC_MODE_WORDS = (
|
||||
"动态模式",
|
||||
"动态座椅",
|
||||
"木马模式",
|
||||
"木马",
|
||||
"摇摇马",
|
||||
"骑木马",
|
||||
"rocking horse",
|
||||
"动态摇篮",
|
||||
"摇篮模式",
|
||||
)
|
||||
|
||||
|
||||
class SeatDemoService:
|
||||
@@ -142,6 +163,8 @@ class SeatDemoService:
|
||||
repository_root / "platforms" / "t2g_seat_bench" / "calibration" / "seat.yaml",
|
||||
)
|
||||
self.hardware_settings = SeatHardwareSettings.from_env(repository_root)
|
||||
self.vehicle_can_settings = VehicleCanSettings.from_env(repository_root)
|
||||
self.native_can_runtime = NativeCanRuntime(repository_root)
|
||||
self.hardware_execution = PHYSICAL_BENCH_COORDINATOR
|
||||
self.hardware_access = PHYSICAL_BENCH_ACCESS
|
||||
# Shared by the short-lived hardware executor instances. A process must
|
||||
@@ -192,8 +215,18 @@ class SeatDemoService:
|
||||
"platform": "reference_vehicle",
|
||||
"execution_mode": "dry_run",
|
||||
"execution_modes": ["dry_run", "simulated_hardware", "hardware"],
|
||||
"hardware_execution_allowed": self.hardware_settings.configured,
|
||||
"hardware_execution_allowed": (
|
||||
self.hardware_settings.configured
|
||||
and self.vehicle_can_settings.configured
|
||||
),
|
||||
"hardware": hardware_info,
|
||||
"vehicle_can": self.vehicle_can_settings.public_info(),
|
||||
"multi_channel_can": {
|
||||
"adapter_ownership": "single_process",
|
||||
"seat_channel": self.hardware_settings.channel,
|
||||
"vehicle_channel": self.vehicle_can_settings.channel,
|
||||
"channels_must_be_distinct": True,
|
||||
},
|
||||
"seat_position_memory_synchronized": bool(
|
||||
self._seat_position_memory_state.get("synchronized", False)
|
||||
),
|
||||
@@ -298,10 +331,13 @@ class SeatDemoService:
|
||||
"actuator_state": context["devices"],
|
||||
}
|
||||
|
||||
def _new_hardware_executor(self) -> HardwareSeatExecutor:
|
||||
def _new_hardware_executor(
|
||||
self, bus_factory: Any | None = None
|
||||
) -> HardwareSeatExecutor:
|
||||
resolved_bus_factory = bus_factory or self.hardware_settings.bus_factory()
|
||||
return HardwareSeatExecutor(
|
||||
self.hardware_matrix,
|
||||
self.hardware_settings.bus_factory(),
|
||||
resolved_bus_factory,
|
||||
thermal_timeout_ms=self.hardware_settings.thermal_timeout_ms,
|
||||
ack_timeout_ms=self.hardware_settings.ack_timeout_ms,
|
||||
axis_maximum_x100={
|
||||
@@ -319,7 +355,7 @@ 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(),
|
||||
firmware_guard=self.hardware_settings.firmware_guard(resolved_bus_factory),
|
||||
synchronize_position_before_first_motion=(
|
||||
self.hardware_settings.position_sync_enabled
|
||||
),
|
||||
@@ -414,8 +450,6 @@ class SeatDemoService:
|
||||
or mode_update is not None
|
||||
)
|
||||
hardware_request = execution_mode == "hardware"
|
||||
if hardware_request and not control_plane_request:
|
||||
self.hardware_settings.authorize(approval_token)
|
||||
conversation_id = conversation_id or f"conv-{uuid.uuid4().hex}"
|
||||
if not CONVERSATION_ID_PATTERN.fullmatch(conversation_id):
|
||||
raise ValueError("conversation_id must start with conv- and contain only lowercase letters, numbers, or hyphens")
|
||||
@@ -512,7 +546,40 @@ class SeatDemoService:
|
||||
context,
|
||||
vehicle_profile,
|
||||
)
|
||||
parsed = self._apply_mode_profiles(parsed, matched_modes, context, vehicle_profile)
|
||||
if self._is_dynamic_mode_stop_request(utterance):
|
||||
parsed = self._apply_dynamic_mode_stop_request(parsed, utterance)
|
||||
else:
|
||||
parsed = self._apply_mode_profiles(
|
||||
parsed, matched_modes, context, vehicle_profile
|
||||
)
|
||||
parsed = merge_native_can_intent(parsed, utterance)
|
||||
parsed = self._partition_native_actions(
|
||||
parsed, promote_supported=hardware_request
|
||||
)
|
||||
if hardware_request and not control_plane_request:
|
||||
if parsed.get("actions"):
|
||||
self.hardware_settings.authorize(approval_token)
|
||||
if parsed.get("native_can_actions"):
|
||||
self.vehicle_can_settings.authorize(approval_token)
|
||||
if not parsed.get("actions") and not parsed.get("native_can_actions"):
|
||||
raise HardwareSafetyError(
|
||||
"hardware mode requires at least one verified seat or vehicle CAN operation"
|
||||
)
|
||||
if parsed.get("actions") and parsed.get("native_can_actions"):
|
||||
if self.hardware_settings.backend != "zlgcan":
|
||||
raise HardwareSafetyError(
|
||||
"linked seat/vehicle execution requires the shared zlgcan backend"
|
||||
)
|
||||
try:
|
||||
seat_channel = int(self.hardware_settings.channel, 0)
|
||||
except ValueError as exc:
|
||||
raise HardwareSafetyError(
|
||||
"ZLG seat CAN channel must be an integer"
|
||||
) from exc
|
||||
if seat_channel == self.vehicle_can_settings.channel:
|
||||
raise HardwareSafetyError(
|
||||
"seat and vehicle CAN must use different adapter channels"
|
||||
)
|
||||
self._trace(
|
||||
traces,
|
||||
request_id,
|
||||
@@ -580,14 +647,9 @@ class SeatDemoService:
|
||||
},
|
||||
)
|
||||
|
||||
if parsed.get("domain_actions") or any(
|
||||
if parsed.get("domain_actions") or parsed.get("native_can_actions") or any(
|
||||
domain != "seat" for domain in detected_domains
|
||||
):
|
||||
if hardware_request and not parsed.get("actions"):
|
||||
raise HardwareSafetyError(
|
||||
"hardware mode requires at least one passenger seat operation; "
|
||||
"non-seat-only requests stay dry-run"
|
||||
)
|
||||
return self._process_orchestrated(
|
||||
utterance,
|
||||
request_id,
|
||||
@@ -1405,6 +1467,63 @@ class SeatDemoService:
|
||||
)
|
||||
return merged
|
||||
|
||||
@staticmethod
|
||||
def _is_dynamic_mode_stop_request(utterance: str) -> bool:
|
||||
compact = re.sub(r"\s+", "", utterance)
|
||||
if any(negation in compact for negation in ("不要停止", "无需停止", "别停止")):
|
||||
return False
|
||||
return any(word in compact for word in DYNAMIC_MODE_STOP_WORDS) and any(
|
||||
word in compact for word in DYNAMIC_MODE_WORDS
|
||||
)
|
||||
|
||||
def _apply_dynamic_mode_stop_request(
|
||||
self,
|
||||
parsed: dict[str, Any],
|
||||
utterance: str,
|
||||
) -> dict[str, Any]:
|
||||
"""Make explicit stop language authoritative over a matched start profile."""
|
||||
|
||||
stopped = deepcopy(parsed)
|
||||
stopped["actions"] = [
|
||||
action
|
||||
for action in stopped.get("actions", [])
|
||||
if action.get("action_type") not in {"dynamic_mode", "dynamic_mode_stop"}
|
||||
]
|
||||
stopped["actions"].append(
|
||||
{"action_type": "dynamic_mode_stop", "target": "passenger"}
|
||||
)
|
||||
stopped["model_requests"] = [
|
||||
request
|
||||
for request in stopped.get("model_requests", [])
|
||||
if request.get("capability")
|
||||
not in {"seat.start_dynamic_mode", DYNAMIC_MODE_STOP_CAPABILITY}
|
||||
]
|
||||
stopped["model_requests"].append(
|
||||
{
|
||||
"domain": "seat",
|
||||
"capability": DYNAMIC_MODE_STOP_CAPABILITY,
|
||||
"target_scope": "passenger",
|
||||
"operation": "deactivate",
|
||||
"parameters": [],
|
||||
"reason": "用户明确要求停止动态座椅循环。",
|
||||
"source_span": utterance,
|
||||
}
|
||||
)
|
||||
stopped["ambiguities"] = [
|
||||
item
|
||||
for item in stopped.get("ambiguities", [])
|
||||
if item.get("field") != "capability"
|
||||
]
|
||||
stopped["detected_domains"] = self._merge_domains(
|
||||
["seat"], stopped.get("detected_domains", [])
|
||||
)
|
||||
stopped["decision_summary"] = [
|
||||
"识别到停止动态座椅模式指令;不会重新启动同名长期模式。",
|
||||
*stopped.get("decision_summary", []),
|
||||
]
|
||||
stopped["confidence"] = max(float(stopped.get("confidence", 0)), 0.99)
|
||||
return stopped
|
||||
|
||||
@staticmethod
|
||||
def _contains_seat_preset_request(parsed: dict[str, Any]) -> bool:
|
||||
capabilities = {
|
||||
@@ -1454,6 +1573,19 @@ class SeatDemoService:
|
||||
action.get("target"),
|
||||
action.get("axis"),
|
||||
)
|
||||
if action.get("action_type") == "dynamic_mode_stop":
|
||||
return (
|
||||
"seat",
|
||||
"dynamic_mode_stop",
|
||||
action.get("target"),
|
||||
)
|
||||
if action.get("action_type") == "dynamic_mode":
|
||||
return (
|
||||
"seat",
|
||||
"dynamic_mode",
|
||||
action.get("target"),
|
||||
action.get("mode_id"),
|
||||
)
|
||||
return (
|
||||
"seat",
|
||||
"function",
|
||||
@@ -2136,6 +2268,24 @@ class SeatDemoService:
|
||||
"parameters": parameters,
|
||||
"reason": "用户固化的座椅位置模式动作。",
|
||||
}
|
||||
if action.get("action_type") == "dynamic_mode_stop":
|
||||
return {
|
||||
"domain": "seat",
|
||||
"capability": DYNAMIC_MODE_STOP_CAPABILITY,
|
||||
"target_scope": action["target"],
|
||||
"operation": "deactivate",
|
||||
"parameters": {},
|
||||
"reason": "用户固化的动态座椅停止动作。",
|
||||
}
|
||||
if action.get("action_type") == "dynamic_mode":
|
||||
return {
|
||||
"domain": "seat",
|
||||
"capability": "seat.start_dynamic_mode",
|
||||
"target_scope": action["target"],
|
||||
"operation": "set",
|
||||
"parameters": {"mode_id": int(action["mode_id"])},
|
||||
"reason": "用户固化的固件原生动态座椅模式。",
|
||||
}
|
||||
function = action.get("function")
|
||||
capability = SEAT_FUNCTION_TO_CAPABILITY.get(str(function))
|
||||
if capability is None:
|
||||
@@ -2152,6 +2302,71 @@ class SeatDemoService:
|
||||
"reason": "用户固化的座椅舒适功能模式动作。",
|
||||
}
|
||||
|
||||
def _partition_native_actions(
|
||||
self, parsed: dict[str, Any], *, promote_supported: bool = False
|
||||
) -> dict[str, Any]:
|
||||
"""Promote verified CAN_DEV capabilities and leave unsupported domains virtual."""
|
||||
|
||||
result = deepcopy(parsed)
|
||||
native_actions = list(result.get("native_can_actions", []))
|
||||
native_signatures = {
|
||||
(
|
||||
str(action.get("domain")),
|
||||
str(action.get("capability")),
|
||||
str(action.get("target", action.get("target_scope", "vehicle"))),
|
||||
json.dumps(action.get("parameters", {}), sort_keys=True, ensure_ascii=False),
|
||||
)
|
||||
for action in native_actions
|
||||
}
|
||||
virtual_actions: list[dict[str, Any]] = []
|
||||
for action in result.get("domain_actions", []):
|
||||
signature = (
|
||||
str(action.get("domain")),
|
||||
str(action.get("capability")),
|
||||
str(action.get("target", action.get("target_scope", "vehicle"))),
|
||||
json.dumps(action.get("parameters", {}), sort_keys=True, ensure_ascii=False),
|
||||
)
|
||||
if signature in native_signatures:
|
||||
continue
|
||||
if promote_supported and self.native_can_runtime.supports(action):
|
||||
promoted = dict(action)
|
||||
promoted["execution_backend"] = "native_can"
|
||||
native_actions.append(promoted)
|
||||
else:
|
||||
virtual_actions.append(action)
|
||||
|
||||
deduplicated: list[dict[str, Any]] = []
|
||||
signatures: set[tuple[str, str, str, str]] = set()
|
||||
for action in native_actions:
|
||||
signature = (
|
||||
str(action.get("domain")),
|
||||
str(action.get("capability")),
|
||||
str(action.get("target", action.get("target_scope", "vehicle"))),
|
||||
json.dumps(action.get("parameters", {}), sort_keys=True, ensure_ascii=False),
|
||||
)
|
||||
if signature not in signatures:
|
||||
signatures.add(signature)
|
||||
deduplicated.append(action)
|
||||
|
||||
result["domain_actions"] = virtual_actions
|
||||
result["native_can_actions"] = deduplicated
|
||||
detected_domains = list(result.get("detected_domains", []))
|
||||
if result.get("actions") and "seat" not in detected_domains:
|
||||
detected_domains.insert(0, "seat")
|
||||
for action in [*virtual_actions, *deduplicated]:
|
||||
if action["domain"] not in detected_domains:
|
||||
detected_domains.append(action["domain"])
|
||||
result["detected_domains"] = detected_domains
|
||||
result["deferred_requests"] = [
|
||||
item
|
||||
for item in result.get("deferred_requests", [])
|
||||
if not (
|
||||
item.get("execution_backend") == "native_can"
|
||||
and self.native_can_runtime.supports(item)
|
||||
)
|
||||
]
|
||||
return result
|
||||
|
||||
def _process_orchestrated(
|
||||
self,
|
||||
utterance: str,
|
||||
@@ -2172,10 +2387,17 @@ class SeatDemoService:
|
||||
vehicle_profile = vehicle_profile or self.vehicle_profiles.default()
|
||||
generation_started = time.perf_counter()
|
||||
intent_ir = build_intent_ir(parsed, utterance, request_id, provider_used)
|
||||
for action in parsed.get("domain_actions", []):
|
||||
capability = self.domain_runtimes[action["domain"]].capabilities[
|
||||
action["capability"]
|
||||
]
|
||||
intent_actions = [
|
||||
*parsed.get("domain_actions", []),
|
||||
*parsed.get("native_can_actions", []),
|
||||
]
|
||||
for action in intent_actions:
|
||||
if action in parsed.get("native_can_actions", []):
|
||||
capability = self.native_can_runtime.capabilities[action["capability"]]
|
||||
else:
|
||||
capability = self.domain_runtimes[action["domain"]].capabilities[
|
||||
action["capability"]
|
||||
]
|
||||
intent_ir["spec"]["requests"].append(
|
||||
{
|
||||
"domain": action["domain"],
|
||||
@@ -2212,6 +2434,20 @@ class SeatDemoService:
|
||||
provider_used,
|
||||
)
|
||||
|
||||
native_config_keys: set[str] = set()
|
||||
native_actions_by_domain: dict[str, list[dict[str, Any]]] = {}
|
||||
for action in parsed.get("native_can_actions", []):
|
||||
native_actions_by_domain.setdefault(action["domain"], []).append(action)
|
||||
for domain, actions in native_actions_by_domain.items():
|
||||
key = f"native_{domain}"
|
||||
native_config_keys.add(key)
|
||||
configs[key] = self.native_can_runtime.build_function_config(
|
||||
domain,
|
||||
actions,
|
||||
request_id,
|
||||
provider_used,
|
||||
)
|
||||
|
||||
result["function_configs"] = configs
|
||||
result["function_config"] = (
|
||||
configs.get("seat") or next(iter(configs.values()), None)
|
||||
@@ -2252,7 +2488,8 @@ class SeatDemoService:
|
||||
"message": ambiguity["question"],
|
||||
}
|
||||
)
|
||||
validation.append(
|
||||
if not native_config_keys:
|
||||
validation.append(
|
||||
{
|
||||
"severity": "warning",
|
||||
"code": "VIRTUAL_EXECUTION_ONLY",
|
||||
@@ -2269,8 +2506,8 @@ class SeatDemoService:
|
||||
"code": "NO_EXECUTABLE_OPERATION",
|
||||
"path": "spec.operations",
|
||||
"message": "模型没有生成任何可执行领域操作。",
|
||||
}
|
||||
)
|
||||
}
|
||||
)
|
||||
if hardware_request:
|
||||
self._adapt_validation_for_hardware(validation)
|
||||
result["validation"] = validation
|
||||
@@ -2310,6 +2547,8 @@ class SeatDemoService:
|
||||
)
|
||||
else:
|
||||
plans[domain] = self.compiler.compile(config)
|
||||
elif domain in native_config_keys:
|
||||
plans[domain] = self.native_can_runtime.compile(config)
|
||||
else:
|
||||
runtime = self.domain_runtimes[domain]
|
||||
plans[domain] = runtime.compile(config)
|
||||
@@ -2388,37 +2627,47 @@ class SeatDemoService:
|
||||
|
||||
execute_started = time.perf_counter()
|
||||
execution_started_at = _now()
|
||||
reports: dict[str, dict[str, Any]] = {}
|
||||
for domain, config in configs.items():
|
||||
if domain == "seat":
|
||||
if hardware_request:
|
||||
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"]
|
||||
)
|
||||
else:
|
||||
reports[domain] = self.domain_runtimes[domain].execute(
|
||||
plans[domain], config
|
||||
)
|
||||
reports = self._execute_orchestrated_reports(
|
||||
configs,
|
||||
plans,
|
||||
native_config_keys,
|
||||
context,
|
||||
hardware_request=hardware_request,
|
||||
simulated_hardware_request=simulated_hardware_request,
|
||||
simulation_fault=simulation_fault,
|
||||
)
|
||||
report_results = [
|
||||
item
|
||||
for domain_report in reports.values()
|
||||
for item in domain_report["spec"]["results"]
|
||||
]
|
||||
operation_results = [
|
||||
item
|
||||
for item in report_results
|
||||
if item.get("ref") != "seat-controller-firmware"
|
||||
]
|
||||
operation_domains = {
|
||||
str(item.get("domain")) for item in operation_results
|
||||
}
|
||||
successful_domains = {
|
||||
domain
|
||||
for domain in operation_domains
|
||||
if all(
|
||||
item.get("status") == "succeeded"
|
||||
for item in operation_results
|
||||
if str(item.get("domain")) == domain
|
||||
)
|
||||
}
|
||||
failed_domains = {
|
||||
str(item.get("domain"))
|
||||
for item in operation_results
|
||||
if item.get("status") == "failed"
|
||||
}
|
||||
execution_succeeded = all(
|
||||
domain_report["spec"]["status"] == "succeeded"
|
||||
for domain_report in reports.values()
|
||||
) and all(item.get("status") == "succeeded" for item in report_results)
|
||||
execution_partially_succeeded = bool(successful_domains and failed_domains)
|
||||
physical_state = (
|
||||
reports.get("seat", {}).get("spec", {}).get("physical_state")
|
||||
if hardware_request
|
||||
@@ -2477,6 +2726,8 @@ class SeatDemoService:
|
||||
result["validation"] = validation
|
||||
if report_issues:
|
||||
result["status"] = "execution_report_invalid"
|
||||
elif execution_partially_succeeded:
|
||||
result["status"] = "partially_succeeded"
|
||||
elif not execution_succeeded:
|
||||
result["status"] = "execution_failed"
|
||||
elif parsed.get("deferred_requests"):
|
||||
@@ -2496,7 +2747,14 @@ class SeatDemoService:
|
||||
"副驾座椅已按物理 CAN 回读闭环执行,其他领域已完成虚拟 CAN 执行。"
|
||||
if hardware_request and execution_succeeded
|
||||
else (
|
||||
f"副驾座椅真实执行失败:{failed_execution['message']}"
|
||||
(
|
||||
"跨域指令部分执行成功;成功域="
|
||||
f"{','.join(sorted(successful_domains))},失败域="
|
||||
f"{','.join(sorted(failed_domains))}:"
|
||||
f"{failed_execution['message']}"
|
||||
)
|
||||
if execution_partially_succeeded and failed_execution
|
||||
else f"真实 CAN 执行失败:{failed_execution['message']}"
|
||||
if hardware_request and failed_execution
|
||||
else (
|
||||
f"模拟台架已执行 {len(report_results)} 个领域操作,物理 CAN 未打开。"
|
||||
@@ -2523,19 +2781,40 @@ class SeatDemoService:
|
||||
"failed_domain": (
|
||||
failed_execution.get("domain") if failed_execution else None
|
||||
),
|
||||
"successful_domains": sorted(successful_domains),
|
||||
"failed_domains": sorted(failed_domains),
|
||||
"partially_succeeded": execution_partially_succeeded,
|
||||
},
|
||||
domain="orchestration",
|
||||
)
|
||||
|
||||
if execution_succeeded and not report_issues:
|
||||
seat_actions = parsed.get("actions", [])
|
||||
if hardware_request:
|
||||
if (execution_succeeded or execution_partially_succeeded) and not report_issues:
|
||||
seat_actions = (
|
||||
parsed.get("actions", [])
|
||||
if execution_succeeded or "seat" in successful_domains
|
||||
else []
|
||||
)
|
||||
if hardware_request and seat_actions and "seat" in reports:
|
||||
seat_actions = self._hardware_feedback_actions(
|
||||
seat_actions,
|
||||
configs["seat"],
|
||||
reports["seat"],
|
||||
)
|
||||
all_actions = [*seat_actions, *parsed.get("domain_actions", [])]
|
||||
all_actions = [
|
||||
*seat_actions,
|
||||
*[
|
||||
action
|
||||
for action in parsed.get("domain_actions", [])
|
||||
if execution_succeeded
|
||||
or str(action.get("domain")) in successful_domains
|
||||
],
|
||||
*[
|
||||
action
|
||||
for action in parsed.get("native_can_actions", [])
|
||||
if execution_succeeded
|
||||
or str(action.get("domain")) in successful_domains
|
||||
],
|
||||
]
|
||||
updated_context = self.conversations.apply_successful_actions(
|
||||
conversation_id,
|
||||
utterance,
|
||||
@@ -2559,11 +2838,17 @@ class SeatDemoService:
|
||||
request_id,
|
||||
"state_update",
|
||||
"succeeded",
|
||||
f"跨域会话状态已更新为第 {updated_context['turn_count']} 轮。",
|
||||
(
|
||||
f"跨域会话状态已更新为第 {updated_context['turn_count']} 轮。"
|
||||
if execution_succeeded
|
||||
else "仅成功设备已写入会话状态;失败设备保持执行前状态。"
|
||||
),
|
||||
time.perf_counter(),
|
||||
{
|
||||
"seat_state": updated_context["devices"],
|
||||
"domain_state": updated_context["domain_devices"],
|
||||
"successful_domains": sorted(successful_domains),
|
||||
"failed_domains": sorted(failed_domains),
|
||||
},
|
||||
domain="orchestration",
|
||||
)
|
||||
@@ -2595,6 +2880,111 @@ class SeatDemoService:
|
||||
self._persist(result)
|
||||
return result
|
||||
|
||||
def _execute_orchestrated_reports(
|
||||
self,
|
||||
configs: dict[str, dict[str, Any]],
|
||||
plans: dict[str, dict[str, Any]],
|
||||
native_config_keys: set[str],
|
||||
context: dict[str, Any],
|
||||
*,
|
||||
hardware_request: bool,
|
||||
simulated_hardware_request: bool,
|
||||
simulation_fault: str,
|
||||
) -> dict[str, dict[str, Any]]:
|
||||
reports: dict[str, dict[str, Any]] = {}
|
||||
bridge = None
|
||||
seat_bus_factory = None
|
||||
native_bus_factory = None
|
||||
|
||||
if hardware_request and native_config_keys:
|
||||
channel_configs = [self.vehicle_can_settings.channel_config()]
|
||||
if "seat" in configs:
|
||||
seat_channel = int(self.hardware_settings.channel, 0)
|
||||
channel_configs.insert(
|
||||
0,
|
||||
ZlgChannelConfig(
|
||||
channel=seat_channel,
|
||||
arbitration_bitrate=self.hardware_settings.bitrate,
|
||||
data_bitrate=self.vehicle_can_settings.data_bitrate,
|
||||
can_fd_iso=True,
|
||||
),
|
||||
)
|
||||
bridge = self.vehicle_can_settings.bridge(channel_configs)
|
||||
native_bus_factory = lambda: bridge.channel_bus(
|
||||
self.vehicle_can_settings.channel
|
||||
)
|
||||
if "seat" in configs:
|
||||
seat_channel = int(self.hardware_settings.channel, 0)
|
||||
seat_bus_factory = lambda: bridge.channel_bus(seat_channel)
|
||||
|
||||
try:
|
||||
def execute_native_configs() -> dict[str, dict[str, Any]]:
|
||||
native_reports: dict[str, dict[str, Any]] = {}
|
||||
for key in sorted(native_config_keys):
|
||||
if not hardware_request:
|
||||
native_reports[key] = self.native_can_runtime.execute_virtual(
|
||||
plans[key], configs[key]
|
||||
)
|
||||
continue
|
||||
executor = NativeCanExecutor(
|
||||
native_bus_factory,
|
||||
feedback_timeout_ms=(
|
||||
self.vehicle_can_settings.feedback_timeout_ms
|
||||
),
|
||||
)
|
||||
native_reports[key] = executor.execute(
|
||||
self.native_can_runtime,
|
||||
plans[key],
|
||||
configs[key],
|
||||
)
|
||||
return native_reports
|
||||
|
||||
def execute_seat_config() -> dict[str, Any]:
|
||||
config = configs["seat"]
|
||||
if hardware_request:
|
||||
seat_executor = (
|
||||
self._new_hardware_executor(seat_bus_factory)
|
||||
if seat_bus_factory is not None
|
||||
else self._new_hardware_executor()
|
||||
)
|
||||
return seat_executor.execute(
|
||||
plans["seat"], config, context["devices"]
|
||||
)
|
||||
if simulated_hardware_request:
|
||||
return self.simulated_hardware_executor.execute(
|
||||
plans["seat"],
|
||||
config,
|
||||
context["devices"],
|
||||
fault_scenario=simulation_fault,
|
||||
)
|
||||
return self.executor.execute(
|
||||
plans["seat"], config, context["devices"]
|
||||
)
|
||||
|
||||
if hardware_request and native_config_keys and "seat" in configs:
|
||||
# Both channel views share one adapter owner; executor threads let
|
||||
# the two channels start within normal USB scheduling jitter.
|
||||
with ThreadPoolExecutor(max_workers=2) as pool:
|
||||
native_future = pool.submit(execute_native_configs)
|
||||
seat_future = pool.submit(execute_seat_config)
|
||||
reports.update(native_future.result())
|
||||
reports["seat"] = seat_future.result()
|
||||
else:
|
||||
reports.update(execute_native_configs())
|
||||
if "seat" in configs:
|
||||
reports["seat"] = execute_seat_config()
|
||||
|
||||
for domain, config in configs.items():
|
||||
if domain in native_config_keys or domain == "seat":
|
||||
continue
|
||||
reports[domain] = self.domain_runtimes[domain].execute(
|
||||
plans[domain], config
|
||||
)
|
||||
finally:
|
||||
if bridge is not None:
|
||||
bridge.close()
|
||||
return reports
|
||||
|
||||
@staticmethod
|
||||
def _build_scene_config(
|
||||
request_id: str,
|
||||
|
||||
Reference in New Issue
Block a user