优化无线/有线CAN,以及控制指令解析
This commit is contained in:
@@ -54,9 +54,12 @@ from cognitive_agent.application.model_plan_adapter import ( # noqa: E402
|
||||
SEAT_MOTION_CAPABILITIES,
|
||||
)
|
||||
from cognitive_agent.application.mqtt_ota_seat_executor import ( # noqa: E402
|
||||
MqttOtaSeatExecutor,
|
||||
MqttNativeSeatExecutor,
|
||||
)
|
||||
from cognitive_agent.application.native_can_intent import ( # noqa: E402
|
||||
DRIVER_SEAT_MASSAGE_CONTINUATION_PATTERN,
|
||||
merge_native_can_intent,
|
||||
)
|
||||
from cognitive_agent.application.native_can_intent import merge_native_can_intent # noqa: E402
|
||||
from cognitive_agent.application.direct_can_control import ( # noqa: E402
|
||||
build_direct_can_intent,
|
||||
)
|
||||
@@ -103,7 +106,10 @@ from cognitive_agent.infrastructure.can.hardware import ( # noqa: E402
|
||||
VehicleCanSettings,
|
||||
VehicleMqttSettings,
|
||||
)
|
||||
from cognitive_agent.infrastructure.can.gateway import ZlgChannelConfig # noqa: E402
|
||||
from cognitive_agent.infrastructure.can.gateway import ( # noqa: E402
|
||||
ZlgCanBridgeProcess,
|
||||
ZlgChannelConfig,
|
||||
)
|
||||
from cognitive_agent.infrastructure.can.mqtt_transport import ( # noqa: E402
|
||||
MqttCanTransport,
|
||||
)
|
||||
@@ -153,6 +159,88 @@ def _wireless_mqtt_publish_preflight() -> dict[str, Any]:
|
||||
}
|
||||
|
||||
|
||||
EXECUTION_VERIFICATION_STATUSES = frozenset(
|
||||
{"verified_success", "submitted_unverified", "failed_or_blocked"}
|
||||
)
|
||||
|
||||
|
||||
def _operation_verification_status(item: dict[str, Any]) -> str:
|
||||
"""Classify one operation without conflating Tx and physical feedback."""
|
||||
|
||||
telemetry = item.get("telemetry", {})
|
||||
explicit = str(telemetry.get("verification_status") or "")
|
||||
if explicit in EXECUTION_VERIFICATION_STATUSES:
|
||||
return explicit
|
||||
if item.get("status") != "succeeded":
|
||||
return "failed_or_blocked"
|
||||
if (
|
||||
telemetry.get("physical_effect_confirmed") is True
|
||||
or telemetry.get("feedback_frame") is not None
|
||||
or telemetry.get("hardware_feedback") is True
|
||||
or telemetry.get("firmware_verified") is True
|
||||
):
|
||||
return "verified_success"
|
||||
if (
|
||||
telemetry.get("command_frame") is not None
|
||||
or telemetry.get("delivery_status") in {"submitted", "queued", "published"}
|
||||
):
|
||||
return "submitted_unverified"
|
||||
# Dry-run and control-plane operations are complete at their own evidence
|
||||
# level and do not claim that a physical actuator moved.
|
||||
return "verified_success"
|
||||
|
||||
|
||||
def _annotate_execution_report(report: dict[str, Any]) -> tuple[str, bool]:
|
||||
"""Add task-level evidence and normalize submission success.
|
||||
|
||||
Returns ``(verification_status, partially_succeeded)``. Support steps such
|
||||
as firmware identity checks do not turn a failed requested operation into a
|
||||
partially successful user task.
|
||||
"""
|
||||
|
||||
spec = report.setdefault("spec", {})
|
||||
results = [item for item in spec.get("results", []) if isinstance(item, dict)]
|
||||
operation_results = [
|
||||
item
|
||||
for item in results
|
||||
if item.get("ref") not in {"seat-controller-firmware"}
|
||||
] or results
|
||||
statuses_by_ref: dict[str, list[str]] = {}
|
||||
for item in operation_results:
|
||||
statuses_by_ref.setdefault(str(item.get("ref") or "operation"), []).append(
|
||||
_operation_verification_status(item)
|
||||
)
|
||||
statuses = [
|
||||
(
|
||||
"failed_or_blocked"
|
||||
if "failed_or_blocked" in values
|
||||
else "submitted_unverified"
|
||||
if "submitted_unverified" in values
|
||||
else "verified_success"
|
||||
)
|
||||
for values in statuses_by_ref.values()
|
||||
]
|
||||
failed = sum(status == "failed_or_blocked" for status in statuses)
|
||||
accepted = len(statuses) - failed
|
||||
partially_succeeded = bool(failed and accepted)
|
||||
verification_status = (
|
||||
"failed_or_blocked"
|
||||
if failed
|
||||
else "submitted_unverified"
|
||||
if any(status == "submitted_unverified" for status in statuses)
|
||||
else "verified_success"
|
||||
)
|
||||
spec["verification_status"] = verification_status
|
||||
spec["status"] = (
|
||||
"partially_succeeded"
|
||||
if partially_succeeded
|
||||
else "failed"
|
||||
if failed
|
||||
else "succeeded"
|
||||
)
|
||||
return verification_status, partially_succeeded
|
||||
|
||||
|
||||
def _has_mixed_driver_passenger_seat_scope(utterance: str) -> bool:
|
||||
"""Detect one request that crosses the CH1-driver and CH0-passenger boundary."""
|
||||
|
||||
@@ -358,6 +446,17 @@ class SeatDemoService:
|
||||
# the physical passenger-seat path through the shared adapter.
|
||||
state_sync_info["poll_interval_ms"] = 5_000
|
||||
vehicle_can_info["state_sync"] = state_sync_info
|
||||
elif self.vehicle_can_settings.state_configured:
|
||||
vehicle_can_info["state_sync"] = {
|
||||
"mode": "on_demand_shared_adapter",
|
||||
"state": "ready",
|
||||
"read_only": True,
|
||||
"poll_interval_ms": 5_000,
|
||||
"snapshot_freshness_ms": 5_000,
|
||||
"capture_sequence": None,
|
||||
"observed_frame_ids": [],
|
||||
"last_error": None,
|
||||
}
|
||||
vehicle_mqtt_info = self._vehicle_mqtt_transport.public_info()
|
||||
return {
|
||||
"service": "cabin-cognitive-orchestrator",
|
||||
@@ -383,6 +482,16 @@ class SeatDemoService:
|
||||
"id": WIRED_CAN_TRANSPORT,
|
||||
"label": "有线控制",
|
||||
**vehicle_can_info,
|
||||
"target_policy": (
|
||||
"dual_channel_wired_can0_passenger_can1_vehicle"
|
||||
if self.hardware_settings.configured
|
||||
and self.vehicle_can_settings.configured
|
||||
else vehicle_can_info.get("target_policy")
|
||||
),
|
||||
"channels": {
|
||||
"seat": 0,
|
||||
"vehicle": self.vehicle_can_settings.channel,
|
||||
},
|
||||
},
|
||||
{
|
||||
"id": WIRELESS_MQTT_TRANSPORT,
|
||||
@@ -411,7 +520,7 @@ class SeatDemoService:
|
||||
and self.vehicle_can_settings.enabled
|
||||
),
|
||||
"active_policy": (
|
||||
"dual_channel_passenger_ch0_driver_ch1"
|
||||
"dual_channel_wired_can0_passenger_can1_vehicle"
|
||||
if self.hardware_settings.configured
|
||||
and self.vehicle_can_settings.configured
|
||||
else "ch1_body_can_driver_only"
|
||||
@@ -759,6 +868,7 @@ class SeatDemoService:
|
||||
)
|
||||
control_transport = self._validate_control_transport(control_transport)
|
||||
mqtt_selected = control_transport == WIRELESS_MQTT_TRANSPORT
|
||||
wired_state_bridge = None
|
||||
if mqtt_selected:
|
||||
if not self.vehicle_mqtt_settings.state_configured:
|
||||
raise HardwareSafetyError("the MQTT CAN0 seat relay is not configured")
|
||||
@@ -768,13 +878,40 @@ class SeatDemoService:
|
||||
)
|
||||
else:
|
||||
if not self.hardware_settings.configured:
|
||||
raise HardwareSafetyError("the T2G seat hardware runtime is not configured")
|
||||
bus_factory = None
|
||||
raise HardwareSafetyError(
|
||||
"the passenger-seat CAN0 hardware runtime is not configured"
|
||||
)
|
||||
if (
|
||||
self.hardware_settings.native_passenger_can0
|
||||
and self.vehicle_can_settings.adapter_configured
|
||||
):
|
||||
wired_state_bridge = self.vehicle_can_settings.bridge(
|
||||
[
|
||||
ZlgChannelConfig(
|
||||
channel=0,
|
||||
frame_format="classic_can",
|
||||
arbitration_bitrate=self.hardware_settings.bitrate,
|
||||
data_bitrate=self.vehicle_can_settings.data_bitrate,
|
||||
can_fd_iso=True,
|
||||
termination_enabled=(
|
||||
self.hardware_settings.termination_enabled
|
||||
),
|
||||
)
|
||||
],
|
||||
monitor_only=True,
|
||||
)
|
||||
bus_factory = lambda: wired_state_bridge.channel_bus(0)
|
||||
else:
|
||||
bus_factory = None
|
||||
vehicle_profile = self.vehicle_profiles.get(vehicle_id)
|
||||
physical_state = self._new_hardware_executor(
|
||||
bus_factory,
|
||||
remote_transport=mqtt_selected,
|
||||
).read_physical_state()
|
||||
try:
|
||||
physical_state = self._new_hardware_executor(
|
||||
bus_factory,
|
||||
remote_transport=mqtt_selected,
|
||||
).read_physical_state()
|
||||
finally:
|
||||
if wired_state_bridge is not None:
|
||||
wired_state_bridge.close()
|
||||
if mqtt_selected:
|
||||
physical_state["source"] = "wireless_mqtt_ch0"
|
||||
context = self.conversations.synchronize_physical_passenger(
|
||||
@@ -1006,12 +1143,16 @@ class SeatDemoService:
|
||||
bus_factory: Any | None = None,
|
||||
*,
|
||||
remote_transport: bool = False,
|
||||
) -> HardwareSeatExecutor | MqttOtaSeatExecutor | ZcanBridgeSeatExecutor:
|
||||
) -> HardwareSeatExecutor | MqttNativeSeatExecutor | ZcanBridgeSeatExecutor:
|
||||
shared_bridge_enabled = os.getenv(
|
||||
"COGNITIVE_AGENT_SHARED_CAN_RUNTIME_ENABLED",
|
||||
os.getenv("COGNITIVE_AGENT_SEAT_SHARED_ZCANPRO_BRIDGE_ENABLED", "true"),
|
||||
).strip().lower() in {"1", "true", "yes", "on"}
|
||||
if bus_factory is None and shared_bridge_enabled:
|
||||
if (
|
||||
bus_factory is None
|
||||
and shared_bridge_enabled
|
||||
and self.hardware_settings.backend != "zlgcan-linux"
|
||||
):
|
||||
# Import lazily so the ordinary dry-run service does not initialize
|
||||
# any self-repair runtime state. Both modules then share the one
|
||||
# process that is allowed to own the Windows ZLG adapter.
|
||||
@@ -1054,22 +1195,72 @@ class SeatDemoService:
|
||||
stop_all_motor_selector=int(
|
||||
self.hardware_compiler.protocol["stop_all_motor_selector"]
|
||||
),
|
||||
auto_home=self.hardware_settings.auto_home_enabled,
|
||||
auto_home=(
|
||||
self.hardware_settings.auto_home_enabled
|
||||
if not remote_transport
|
||||
else False
|
||||
),
|
||||
home_timeout_s=self.hardware_settings.home_timeout_s,
|
||||
firmware_guard=self.hardware_settings.firmware_guard(
|
||||
resolved_bus_factory,
|
||||
allow_programming=not remote_transport,
|
||||
firmware_guard=(
|
||||
None
|
||||
if remote_transport
|
||||
or self.hardware_settings.native_passenger_can0
|
||||
else self.hardware_settings.firmware_guard(
|
||||
resolved_bus_factory,
|
||||
allow_programming=not remote_transport,
|
||||
)
|
||||
),
|
||||
synchronize_position_before_first_motion=(
|
||||
self.hardware_settings.position_sync_enabled
|
||||
if not remote_transport
|
||||
else False
|
||||
),
|
||||
position_sync_state=self._seat_position_memory_state,
|
||||
require_thermal_feedback=not remote_transport,
|
||||
feedback_required=not remote_transport,
|
||||
)
|
||||
if remote_transport:
|
||||
return MqttOtaSeatExecutor(executor, resolved_bus_factory)
|
||||
return MqttNativeSeatExecutor(executor, resolved_bus_factory)
|
||||
return executor
|
||||
|
||||
def _new_linux_wired_dual_channel_bridge(self) -> ZlgCanBridgeProcess:
|
||||
"""Open CAN0 passenger classic CAN and CAN1 body CAN FD under one owner."""
|
||||
|
||||
if not self.hardware_settings.native_passenger_can0:
|
||||
raise HardwareSafetyError(
|
||||
"the shared Linux dual-channel bridge requires native passenger CAN0"
|
||||
)
|
||||
if not self.vehicle_can_settings.configured:
|
||||
raise HardwareSafetyError(
|
||||
"the shared Linux dual-channel bridge requires configured vehicle CAN1"
|
||||
)
|
||||
if (
|
||||
self.hardware_settings.device_type
|
||||
!= self.vehicle_can_settings.device_type
|
||||
or self.hardware_settings.device_index
|
||||
!= self.vehicle_can_settings.device_index
|
||||
or self.hardware_settings.zlg_dll.resolve()
|
||||
!= self.vehicle_can_settings.zlg_dll.resolve()
|
||||
):
|
||||
raise HardwareSafetyError(
|
||||
"CAN0 and CAN1 must use the same Linux ZLG device and SDK library"
|
||||
)
|
||||
return self.vehicle_can_settings.bridge(
|
||||
[
|
||||
ZlgChannelConfig(
|
||||
channel=0,
|
||||
frame_format="classic_can",
|
||||
arbitration_bitrate=self.hardware_settings.bitrate,
|
||||
data_bitrate=self.vehicle_can_settings.data_bitrate,
|
||||
can_fd_iso=True,
|
||||
termination_enabled=(
|
||||
self.hardware_settings.termination_enabled
|
||||
),
|
||||
),
|
||||
self.vehicle_can_settings.channel_config(),
|
||||
]
|
||||
)
|
||||
|
||||
def preview_native_can(
|
||||
self,
|
||||
utterance: str,
|
||||
@@ -1867,10 +2058,11 @@ class SeatDemoService:
|
||||
if parsed.get("actions") and parsed.get("native_can_actions"):
|
||||
if (
|
||||
control_transport == WIRED_CAN_TRANSPORT
|
||||
and self.hardware_settings.backend != "zlgcan"
|
||||
and self.hardware_settings.backend
|
||||
not in {"zlgcan", "zlgcan-linux"}
|
||||
):
|
||||
raise HardwareSafetyError(
|
||||
"linked seat/vehicle execution requires the shared zlgcan backend"
|
||||
"linked seat/vehicle execution requires a shared ZLG backend"
|
||||
)
|
||||
if control_transport == WIRED_CAN_TRANSPORT:
|
||||
try:
|
||||
@@ -2282,7 +2474,7 @@ class SeatDemoService:
|
||||
"safety_check",
|
||||
"succeeded",
|
||||
(
|
||||
"无线 MQTT CAN0 座椅语义白名单与 seat-ota 报文白名单已通过;"
|
||||
"无线 MQTT CAN0 座椅语义白名单与固件原生 0x300 报文白名单已通过;"
|
||||
"本地 T2G 固件 profile、USB-CAN 与物理台架连接不参与校验。"
|
||||
if hardware_request
|
||||
and control_transport == WIRELESS_MQTT_TRANSPORT
|
||||
@@ -2307,7 +2499,7 @@ class SeatDemoService:
|
||||
simulation_fault if simulated_hardware_request else None
|
||||
),
|
||||
"position_calibration_gate": (
|
||||
"remote_seat_ota_feedback"
|
||||
"remote_native_can0_feedback"
|
||||
if hardware_request
|
||||
and control_transport == WIRELESS_MQTT_TRANSPORT
|
||||
else "runtime_can_preflight"
|
||||
@@ -2315,7 +2507,7 @@ class SeatDemoService:
|
||||
else "not_applicable"
|
||||
),
|
||||
"mapping_status": (
|
||||
"mqtt_can0_seat_ota_whitelist"
|
||||
"mqtt_can0_native_firmware_whitelist"
|
||||
if hardware_request
|
||||
and control_transport == WIRELESS_MQTT_TRANSPORT
|
||||
else "protocol_hil_verified_combined_firmware_hil_pending"
|
||||
@@ -2335,17 +2527,47 @@ class SeatDemoService:
|
||||
if hardware_request:
|
||||
mqtt_selected = control_transport == WIRELESS_MQTT_TRANSPORT
|
||||
vehicle_safety_preflight = None
|
||||
if mqtt_selected:
|
||||
vehicle_safety_preflight = _wireless_mqtt_publish_preflight()
|
||||
hardware_executor = self._new_hardware_executor(
|
||||
lambda: self._vehicle_mqtt_transport.bus(channel=0),
|
||||
remote_transport=True,
|
||||
wired_bridge = None
|
||||
try:
|
||||
if mqtt_selected:
|
||||
vehicle_safety_preflight = _wireless_mqtt_publish_preflight()
|
||||
hardware_executor = self._new_hardware_executor(
|
||||
lambda: self._vehicle_mqtt_transport.bus(channel=0),
|
||||
remote_transport=True,
|
||||
)
|
||||
elif (
|
||||
self.hardware_settings.native_passenger_can0
|
||||
and self.vehicle_can_settings.configured
|
||||
):
|
||||
wired_bridge = self._new_linux_wired_dual_channel_bridge()
|
||||
vehicle_safety_preflight = observe_vehicle_safety(
|
||||
wired_bridge.channel_bus(1),
|
||||
timeout_ms=min(
|
||||
5_000,
|
||||
max(
|
||||
1_000,
|
||||
self.vehicle_can_settings.feedback_timeout_ms,
|
||||
),
|
||||
),
|
||||
required_channel=1,
|
||||
)
|
||||
require_stationary_vehicle(vehicle_safety_preflight)
|
||||
hardware_executor = self._new_hardware_executor(
|
||||
lambda: wired_bridge.channel_bus(0)
|
||||
)
|
||||
else:
|
||||
hardware_executor = self._new_hardware_executor()
|
||||
execution_report = hardware_executor.execute(
|
||||
command_plan, function_config, context["devices"]
|
||||
)
|
||||
else:
|
||||
hardware_executor = self._new_hardware_executor()
|
||||
execution_report = hardware_executor.execute(
|
||||
command_plan, function_config, context["devices"]
|
||||
)
|
||||
except VehicleSafetyPreflightError as exc:
|
||||
raise HardwareSafetyError(
|
||||
"CH1 vehicle safety preflight refused passenger CAN0 execution: "
|
||||
f"{exc}"
|
||||
) from exc
|
||||
finally:
|
||||
if wired_bridge is not None:
|
||||
wired_bridge.close()
|
||||
if vehicle_safety_preflight is not None:
|
||||
for item in execution_report["spec"].get("results", []):
|
||||
item.setdefault("telemetry", {})[
|
||||
@@ -2362,14 +2584,21 @@ class SeatDemoService:
|
||||
execution_report = self.executor.execute(
|
||||
command_plan, function_config, context["devices"]
|
||||
)
|
||||
execution_outcome, execution_partially_succeeded = (
|
||||
_annotate_execution_report(execution_report)
|
||||
)
|
||||
report_issues = self.contracts.validate(execution_report)
|
||||
validation.extend(report_issues)
|
||||
result["execution_report"] = execution_report
|
||||
result["execution_outcome"] = execution_outcome
|
||||
execution_succeeded = (
|
||||
not report_issues and execution_report["spec"]["status"] == "succeeded"
|
||||
not report_issues
|
||||
and execution_report["spec"]["status"] == "succeeded"
|
||||
)
|
||||
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"):
|
||||
@@ -2406,7 +2635,11 @@ class SeatDemoService:
|
||||
"execute",
|
||||
"succeeded" if execution_succeeded else "failed",
|
||||
(
|
||||
"真实 CAN 命令已发送,并收到 IAR 固件的实际状态回读。"
|
||||
"真实 CAN 命令已提交;当前反馈窗口未确认车辆效果,状态以实时 CAN 回读为准。"
|
||||
if hardware_request
|
||||
and execution_succeeded
|
||||
and execution_outcome == "submitted_unverified"
|
||||
else "真实 CAN 命令已发送,并收到 IAR 固件的实际状态回读。"
|
||||
if hardware_request and execution_succeeded
|
||||
else (
|
||||
(
|
||||
@@ -2608,12 +2841,18 @@ class SeatDemoService:
|
||||
feedback_by_ref = {
|
||||
item["ref"]: item.get("telemetry", {})
|
||||
for item in execution_report["spec"]["results"]
|
||||
if item.get("status") == "succeeded"
|
||||
if _operation_verification_status(item) == "verified_success"
|
||||
}
|
||||
updated = deepcopy(actions)
|
||||
updated: list[dict[str, Any]] = []
|
||||
operations = function_config["spec"]["operations"]
|
||||
for action, operation in zip(updated, operations, strict=True):
|
||||
telemetry = feedback_by_ref.get(operation["id"], {})
|
||||
for source_action, operation in zip(actions, operations, strict=True):
|
||||
telemetry = feedback_by_ref.get(operation["id"])
|
||||
if telemetry is None:
|
||||
# Submission alone is not an authoritative vehicle-state
|
||||
# update. The asynchronous CAN/MQTT state monitor will apply
|
||||
# the change if and when the vehicle reports it.
|
||||
continue
|
||||
action = deepcopy(source_action)
|
||||
if action.get("action_type") == "position":
|
||||
if telemetry.get("effective_position_pct") is not None:
|
||||
action["position_pct"] = int(
|
||||
@@ -2633,12 +2872,14 @@ class SeatDemoService:
|
||||
if telemetry.get("motion_mode") == "timed_user_request"
|
||||
else "relative_hall_confirmed_open_loop"
|
||||
)
|
||||
updated.append(action)
|
||||
continue
|
||||
raw = telemetry.get("feedback_value")
|
||||
if raw is not None:
|
||||
raw_value = int(raw)
|
||||
action["state"] = "off" if raw_value <= 1 else "on"
|
||||
action["requested_level"] = max(0, raw_value - 1)
|
||||
updated.append(action)
|
||||
|
||||
physical_state = execution_report.get("spec", {}).get("physical_state")
|
||||
if isinstance(physical_state, dict):
|
||||
@@ -4259,6 +4500,8 @@ class SeatDemoService:
|
||||
simulation_fault=simulation_fault,
|
||||
control_transport=control_transport,
|
||||
)
|
||||
for domain_report in reports.values():
|
||||
_annotate_execution_report(domain_report)
|
||||
report_results = [
|
||||
item
|
||||
for domain_report in reports.values()
|
||||
@@ -4276,7 +4519,7 @@ class SeatDemoService:
|
||||
domain
|
||||
for domain in operation_domains
|
||||
if all(
|
||||
item.get("status") == "succeeded"
|
||||
_operation_verification_status(item) != "failed_or_blocked"
|
||||
for item in operation_results
|
||||
if str(item.get("domain")) == domain
|
||||
)
|
||||
@@ -4284,13 +4527,29 @@ class SeatDemoService:
|
||||
failed_domains = {
|
||||
str(item.get("domain"))
|
||||
for item in operation_results
|
||||
if item.get("status") == "failed"
|
||||
if _operation_verification_status(item) == "failed_or_blocked"
|
||||
}
|
||||
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_succeeded = not failed_domains
|
||||
execution_partially_succeeded = bool(successful_domains and failed_domains)
|
||||
execution_outcome = (
|
||||
"failed_or_blocked"
|
||||
if failed_domains
|
||||
else "submitted_unverified"
|
||||
if any(
|
||||
_operation_verification_status(item) == "submitted_unverified"
|
||||
for item in operation_results
|
||||
)
|
||||
else "verified_success"
|
||||
)
|
||||
verified_domains = {
|
||||
domain
|
||||
for domain in operation_domains
|
||||
if all(
|
||||
_operation_verification_status(item) == "verified_success"
|
||||
for item in operation_results
|
||||
if str(item.get("domain")) == domain
|
||||
)
|
||||
}
|
||||
physical_state = (
|
||||
reports.get("seat", {}).get("spec", {}).get("physical_state")
|
||||
if hardware_request
|
||||
@@ -4299,7 +4558,14 @@ class SeatDemoService:
|
||||
simulated_seat = simulated_hardware_request and "seat" in reports
|
||||
report_spec: dict[str, Any] = {
|
||||
"mode": "hil" if hardware_request or simulated_seat else "dry_run",
|
||||
"status": "succeeded" if execution_succeeded else "failed",
|
||||
"status": (
|
||||
"partially_succeeded"
|
||||
if execution_partially_succeeded
|
||||
else "succeeded"
|
||||
if execution_succeeded
|
||||
else "failed"
|
||||
),
|
||||
"verification_status": execution_outcome,
|
||||
"started_at": execution_started_at,
|
||||
"finished_at": _now(),
|
||||
"results": report_results,
|
||||
@@ -4352,6 +4618,7 @@ class SeatDemoService:
|
||||
report_issues = self.contracts.validate(execution_report)
|
||||
validation.extend(report_issues)
|
||||
result["execution_report"] = execution_report
|
||||
result["execution_outcome"] = execution_outcome
|
||||
result["validation"] = validation
|
||||
if report_issues:
|
||||
result["status"] = "execution_report_invalid"
|
||||
@@ -4376,6 +4643,11 @@ class SeatDemoService:
|
||||
"无线 MQTT CAN0/CAN1 白名单控制帧已提交;"
|
||||
"车辆实际状态以状态话题回读为准。"
|
||||
if wireless_hardware_request and execution_succeeded
|
||||
else "有线 CAN0/CAN1 白名单控制帧已提交;"
|
||||
"当前反馈窗口未确认车辆效果,状态以实时 CAN 回读为准。"
|
||||
if hardware_request
|
||||
and execution_succeeded
|
||||
and execution_outcome == "submitted_unverified"
|
||||
else "CH1 车身 CAN 动作已按目标字段物理回读闭环执行。"
|
||||
if hardware_request and native_config_keys and execution_succeeded
|
||||
else "副驾座椅已按独立 T2G 物理 CAN 回读闭环执行。"
|
||||
@@ -4418,6 +4690,7 @@ class SeatDemoService:
|
||||
"successful_domains": sorted(successful_domains),
|
||||
"failed_domains": sorted(failed_domains),
|
||||
"partially_succeeded": execution_partially_succeeded,
|
||||
"execution_outcome": execution_outcome,
|
||||
},
|
||||
domain="orchestration",
|
||||
)
|
||||
@@ -4439,8 +4712,17 @@ class SeatDemoService:
|
||||
*[
|
||||
action
|
||||
for action in parsed.get("domain_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
|
||||
)
|
||||
)
|
||||
or (
|
||||
hardware_request
|
||||
and str(action.get("domain")) in verified_domains
|
||||
)
|
||||
],
|
||||
*[
|
||||
{
|
||||
@@ -4550,8 +4832,16 @@ class SeatDemoService:
|
||||
vehicle_safety_preflight: dict[str, Any] | None = None
|
||||
mqtt_selected = control_transport == WIRELESS_MQTT_TRANSPORT
|
||||
|
||||
wired_native_passenger = bool(
|
||||
not mqtt_selected
|
||||
and "seat" in configs
|
||||
and self.hardware_settings.native_passenger_can0
|
||||
and self.vehicle_can_settings.configured
|
||||
)
|
||||
if hardware_request and (
|
||||
native_config_keys or (mqtt_selected and "seat" in configs)
|
||||
native_config_keys
|
||||
or (mqtt_selected and "seat" in configs)
|
||||
or wired_native_passenger
|
||||
):
|
||||
shared_runtime_enabled = (
|
||||
not mqtt_selected
|
||||
@@ -4584,6 +4874,10 @@ class SeatDemoService:
|
||||
# on CH0; passing a raw bus factory here would bypass its one-
|
||||
# owner queue and allow self-repair to race orchestration.
|
||||
seat_bus_factory = None
|
||||
elif wired_native_passenger:
|
||||
bridge = self._new_linux_wired_dual_channel_bridge()
|
||||
native_bus_factory = lambda: bridge.channel_bus(1)
|
||||
seat_bus_factory = lambda: bridge.channel_bus(0)
|
||||
else:
|
||||
channel_configs = [self.vehicle_can_settings.channel_config()]
|
||||
if "seat" in configs:
|
||||
@@ -4779,11 +5073,22 @@ class SeatDemoService:
|
||||
]
|
||||
clauses: list[str] = []
|
||||
for fragment in fragments:
|
||||
# A massage mode/strength phrase may omit both "主驾" and "按摩"
|
||||
# after the first clause. Keep the whole request atomic so the ON
|
||||
# fallback does not emit relax before an explicit later mode.
|
||||
massage_continuation = bool(
|
||||
clauses
|
||||
and "按摩" in clauses[-1]
|
||||
and DRIVER_SEAT_MASSAGE_CONTINUATION_PATTERN.search(fragment)
|
||||
)
|
||||
# An exclusion is a qualifier of the control immediately before it,
|
||||
# not an independent command. Keeping both pieces together lets
|
||||
# the deterministic window parser remove the excluded targets
|
||||
# before the command list is finalized.
|
||||
if clauses and re.search(r"除外|除去|不包括|不含", fragment):
|
||||
exclusion_qualifier = bool(
|
||||
clauses and re.search(r"除外|除去|不包括|不含", fragment)
|
||||
)
|
||||
if massage_continuation or exclusion_qualifier:
|
||||
clauses[-1] = f"{clauses[-1]},{fragment}"
|
||||
else:
|
||||
clauses.append(fragment)
|
||||
|
||||
Reference in New Issue
Block a user