feat: synchronize CH1 vehicle control and feedback

This commit is contained in:
vee
2026-08-11 10:42:13 +08:00
parent 3701192a11
commit 32379a1573
50 changed files with 13636 additions and 736 deletions
+189 -18
View File
@@ -53,6 +53,12 @@ from cognitive_agent.application.model_plan_adapter import ( # noqa: E402
SEAT_MOTION_CAPABILITIES,
)
from cognitive_agent.application.native_can_intent import merge_native_can_intent # noqa: E402
from cognitive_agent.application.native_can_feedback import ( # noqa: E402
VehicleSafetyPreflightError,
authoritative_physical_ch1_feedback_state,
observe_vehicle_safety,
require_stationary_vehicle,
)
from cognitive_agent.application.native_can_runtime import ( # noqa: E402
NativeCanExecutor,
NativeCanRuntime,
@@ -93,6 +99,27 @@ def _clean_vehicle_number(value: float | None) -> int | float | None:
CONVERSATION_ID_PATTERN = re.compile(r"^conv-[a-z0-9-]{6,80}$")
SEAT_SCOPE_PATTERN = re.compile(r"\bseats?\b|座椅|坐椅", re.IGNORECASE)
DRIVER_SEAT_SCOPE_PATTERN = re.compile(
r"\bdriver\b|\bfront[-_ ]?left\b|\bleft[-_ ]?front\b|主驾|驾驶位|司机位|左前",
re.IGNORECASE,
)
PASSENGER_SEAT_SCOPE_PATTERN = re.compile(
r"\bpassenger\b|\bfront[-_ ]?right\b|\bright[-_ ]?front\b|副驾|乘客|右前",
re.IGNORECASE,
)
def _has_mixed_driver_passenger_seat_scope(utterance: str) -> bool:
"""Detect one request that crosses the CH1-driver and CH0-passenger boundary."""
return bool(
SEAT_SCOPE_PATTERN.search(utterance)
and DRIVER_SEAT_SCOPE_PATTERN.search(utterance)
and PASSENGER_SEAT_SCOPE_PATTERN.search(utterance)
)
SAVE_MODE_PROFILE_NAME = re.compile(
r"(?:固化为|保存为长期|保存成长期|长期保存为|记为长期|命名为)"
r"(?P<name>[^,。,.!!\s]{1,64}?模式)"
@@ -201,6 +228,17 @@ class SeatDemoService:
if hasattr(self, "conversations"):
self.conversations.preset_root = self._artifact_root / "user-presets"
@property
def driver_only_ch1_execution_active(self) -> bool:
"""Return whether deployment policy authorizes only the CH1 vehicle path."""
return bool(
not self.hardware_settings.enabled
and self.vehicle_can_settings.configured
and self.vehicle_can_settings.channel_is_allowed
and self.vehicle_can_settings.channel == 1
)
def runtime_info(self) -> dict[str, Any]:
catalog = yaml.safe_load(
(self.domain_root / "config" / "capabilities.yaml").read_text(encoding="utf-8")
@@ -216,7 +254,9 @@ class SeatDemoService:
"service": "cabin-cognitive-orchestrator",
"domain": "cabin",
"platform": "reference_vehicle",
"execution_mode": "dry_run",
"execution_mode": os.getenv(
"COGNITIVE_AGENT_EXECUTION_MODE", "dry_run"
).strip(),
"execution_modes": ["dry_run", "simulated_hardware", "hardware"],
"hardware_execution_allowed": (
self.hardware_settings.configured
@@ -224,11 +264,26 @@ class SeatDemoService:
),
"hardware": hardware_info,
"vehicle_can": self.vehicle_can_settings.public_info(),
"vehicle_safety_policy": {
"required_channel": 1,
"required_gear": "P",
"maximum_speed_kmh": 0.05,
"fresh_feedback_ids": ["0x342", "0x4EB"],
"preflight": "read_only_before_every_physical_request",
},
"multi_channel_can": {
"adapter_ownership": "single_process",
"seat_channel": self.hardware_settings.channel,
"seat_channel": (
self.hardware_settings.channel
if self.hardware_settings.enabled
else None
),
"vehicle_channel": self.vehicle_can_settings.channel,
"channels_must_be_distinct": True,
"channels_must_be_distinct": bool(
self.hardware_settings.enabled
and self.vehicle_can_settings.enabled
),
"active_policy": "ch1_body_can_driver_only",
},
"seat_position_memory_synchronized": bool(
self._seat_position_memory_state.get("synchronized", False)
@@ -411,7 +466,7 @@ class SeatDemoService:
vehicle_id,
simulation_fault,
)
if hardware_access_bypass:
if hardware_access_bypass or self.driver_only_ch1_execution_active:
with self.hardware_execution.reserve():
return self._process_once(
utterance,
@@ -476,6 +531,11 @@ class SeatDemoService:
or mode_update is not None
)
hardware_request = execution_mode == "hardware"
if hardware_request and _has_mixed_driver_passenger_seat_scope(utterance):
raise HardwareSafetyError(
"hardware mode refuses a mixed CH1 driver/front-left and "
"CH0 passenger/front-right seat request"
)
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")
@@ -584,6 +644,17 @@ class SeatDemoService:
promote_supported=(hardware_request and self.vehicle_can_settings.enabled),
)
if hardware_request and not control_plane_request:
if parsed.get("domain_actions"):
unsupported = sorted(
{
str(action.get("capability", "unknown"))
for action in parsed["domain_actions"]
}
)
raise HardwareSafetyError(
"hardware mode refuses virtual or unverified vehicle actions: "
+ ", ".join(unsupported)
)
if parsed.get("actions"):
self.hardware_settings.authorize(approval_token)
if parsed.get("native_can_actions"):
@@ -1343,6 +1414,20 @@ class SeatDemoService:
action["physical_target_alias"] = "t2g_primary_seat"
return updated
@staticmethod
def _physical_ch1_feedback_states(
reports: dict[str, dict[str, Any]],
) -> list[dict[str, Any]]:
"""Return only fully verified decoded CH1 feedback state patches."""
states: list[dict[str, Any]] = []
for report in reports.values():
for item in report.get("spec", {}).get("results", []):
feedback_state = authoritative_physical_ch1_feedback_state(item)
if feedback_state is not None:
states.append(feedback_state)
return states
@staticmethod
def _parse_mode_profile_save_name(utterance: str) -> str | None:
explicit = SAVE_MODE_PROFILE_NAME.search(utterance)
@@ -2335,7 +2420,12 @@ class SeatDemoService:
"""Promote verified CAN_DEV capabilities and leave unsupported domains virtual."""
result = deepcopy(parsed)
native_actions = list(result.get("native_can_actions", []))
candidate_native_actions = list(result.get("native_can_actions", []))
native_actions = [
action
for action in candidate_native_actions
if self.native_can_runtime.supports(action)
]
native_signatures = {
(
str(action.get("domain")),
@@ -2343,7 +2433,7 @@ class SeatDemoService:
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
for action in candidate_native_actions
}
virtual_actions: list[dict[str, Any]] = []
for action in result.get("domain_actions", []):
@@ -2384,7 +2474,7 @@ class SeatDemoService:
if action["domain"] not in detected_domains:
detected_domains.append(action["domain"])
result["detected_domains"] = detected_domains
result["deferred_requests"] = [
deferred_requests = [
item
for item in result.get("deferred_requests", [])
if not (
@@ -2392,6 +2482,18 @@ class SeatDemoService:
and self.native_can_runtime.supports(item)
)
]
for item in deferred_requests:
if item.get("capability") in {
"door.set_vehicle_lock",
"door.set_rear_lock",
}:
item["execution_backend"] = "unsupported"
item["rejection_code"] = "CH1_DOOR_LOCK_CONTROL_UNSUPPORTED"
item["reason"] = (
"Current CH1 door-lock control is unsupported; lock feedback "
"is observational only and no verified physical command will be built."
)
result["deferred_requests"] = deferred_requests
return result
def _process_orchestrated(
@@ -2410,7 +2512,7 @@ class SeatDemoService:
simulation_fault: str = "normal",
vehicle_profile: VehicleProfile | None = None,
) -> dict[str, Any]:
"""Run isolated adapters, with only the passenger seat eligible for HIL."""
"""Run isolated adapters under the selected physical-bus policy."""
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)
@@ -2626,7 +2728,9 @@ class SeatDemoService:
"safety_check",
"succeeded",
(
"副驾座椅已通过 T2G HIL 安全门,其他座舱领域保持虚拟 CAN。"
"所有动作已通过 CH1 车身 CAN 白名单和物理反馈门禁。"
if hardware_request and native_config_keys and "seat" not in configs
else "副驾 T2G 台架动作已通过独立硬件安全门。"
if hardware_request
else (
"模拟台架仅对座椅结果注入故障;全部领域的物理发送路径保持关闭。"
@@ -2641,11 +2745,16 @@ class SeatDemoService:
"simulation_fault": (
simulation_fault if simulated_hardware_request else None
),
"hardware_domains": ["seat"] if hardware_request else [],
"hardware_domains": (
[configs[key]["spec"]["domain"] for key in native_config_keys]
+ (["seat"] if "seat" in configs else [])
if hardware_request
else []
),
"virtual_domains": [
domain
for domain in configs
if not hardware_request or domain != "seat"
if not hardware_request
],
"domains": list(configs),
},
@@ -2729,7 +2838,9 @@ class SeatDemoService:
"labels": {
"simulation": "mixed" if hardware_request else "true",
"seat_feedback": (
"physical_can"
"physical_can_ch1"
if hardware_request and native_config_keys and "seat" not in configs
else "physical_can"
if hardware_request
else (
"simulated_hardware"
@@ -2771,7 +2882,9 @@ class SeatDemoService:
"execute",
"succeeded" if execution_succeeded and not report_issues else "failed",
(
"副驾座椅已按物理 CAN 回读闭环执行,其他领域已完成虚拟 CAN 执行。"
"CH1 车身 CAN 动作已按目标字段物理回读闭环执行。"
if hardware_request and native_config_keys and execution_succeeded
else "副驾座椅已按独立 T2G 物理 CAN 回读闭环执行。"
if hardware_request and execution_succeeded
else (
(
@@ -2836,10 +2949,20 @@ class SeatDemoService:
or str(action.get("domain")) in successful_domains
],
*[
action
{
**action,
**(
{"feedback_source": "physical_can_ch1"}
if hardware_request
else {}
),
}
for action in parsed.get("native_can_actions", [])
if execution_succeeded
or str(action.get("domain")) in successful_domains
if not hardware_request
and (
execution_succeeded
or str(action.get("domain")) in successful_domains
)
],
]
updated_context = self.conversations.apply_successful_actions(
@@ -2853,6 +2976,13 @@ class SeatDemoService:
conversation_id,
physical_state,
)
if hardware_request:
physical_ch1_feedback = self._physical_ch1_feedback_states(reports)
if physical_ch1_feedback:
updated_context = self.conversations.synchronize_physical_ch1_feedback(
conversation_id,
physical_ch1_feedback,
)
updated_context = self._context_for_vehicle(
updated_context,
vehicle_profile,
@@ -2922,6 +3052,7 @@ class SeatDemoService:
bridge = None
seat_bus_factory = None
native_bus_factory = None
vehicle_safety_preflight: dict[str, Any] | None = None
if hardware_request and native_config_keys:
channel_configs = [self.vehicle_can_settings.channel_config()]
@@ -2944,6 +3075,29 @@ class SeatDemoService:
seat_channel = int(self.hardware_settings.channel, 0)
seat_bus_factory = lambda: bridge.channel_bus(seat_channel)
# The bridge is open but no control frame has been submitted yet.
# Require two independent, fresh CH1 status frames before entering
# any actuator executor; CH0 traffic is ignored by the observer.
try:
vehicle_safety_preflight = observe_vehicle_safety(
native_bus_factory(),
timeout_ms=min(
5_000,
max(1_000, self.vehicle_can_settings.feedback_timeout_ms),
),
required_channel=self.vehicle_can_settings.channel,
)
require_stationary_vehicle(vehicle_safety_preflight)
except VehicleSafetyPreflightError as exc:
bridge.close()
raise HardwareSafetyError(
"CH1 vehicle safety preflight refused physical execution: "
f"{exc}"
) from exc
except Exception:
bridge.close()
raise
try:
def execute_native_configs() -> dict[str, dict[str, Any]]:
native_reports: dict[str, dict[str, Any]] = {}
@@ -2964,6 +3118,13 @@ class SeatDemoService:
plans[key],
configs[key],
)
native_reports[key]["spec"]["vehicle_safety_preflight"] = (
vehicle_safety_preflight
)
for item in native_reports[key]["spec"].get("results", []):
item.setdefault("telemetry", {})[
"vehicle_safety_preflight"
] = vehicle_safety_preflight
return native_reports
def execute_seat_config() -> dict[str, Any]:
@@ -3077,7 +3238,12 @@ class SeatDemoService:
try:
plan = self.openai.interpret(utterance, context, self.capability_context)
return (
self.model_plan_adapter.adapt(plan, context, vehicle_profile),
self.model_plan_adapter.adapt(
plan,
context,
vehicle_profile,
utterance=utterance,
),
f"openai:{self.openai.model}",
None,
)
@@ -3091,7 +3257,12 @@ class SeatDemoService:
try:
plan = self.openai.interpret(utterance, context, self.capability_context)
return (
self.model_plan_adapter.adapt(plan, context, vehicle_profile),
self.model_plan_adapter.adapt(
plan,
context,
vehicle_profile,
utterance=utterance,
),
f"openai:{self.openai.model}",
None,
)