feat: complete dual-seat whole-vehicle orchestration

This commit is contained in:
vee
2026-08-12 15:12:40 +08:00
parent 32379a1573
commit 9ce64b3a01
62 changed files with 9741 additions and 3950 deletions
@@ -1,10 +1,8 @@
"""Execute normal seat command plans through the shared ZCANPRO bridge.
"""Execute normal seat command plans through the shared CAN runtime.
ZLG's Windows driver gives one process exclusive ownership of a USBCAN device.
Self-repair already keeps that ownership inside ZCANPRO, so opening the same
adapter again from the web server makes both user and admin orchestration fail.
This executor keeps one owner and exchanges bounded, armed JSON requests with
the bridge instead.
Self-repair and orchestration therefore exchange bounded, armed requests with
one transport-neutral runtime instead of opening the adapter independently.
"""
from __future__ import annotations
@@ -80,6 +78,10 @@ class ZcanBridgeSeatExecutor:
return value
def _wait_online(self, *, require_protocol: bool) -> dict[str, Any]:
if require_protocol and hasattr(
self.bridge_manager, "ensure_seat_protocol"
):
return self.bridge_manager.ensure_seat_protocol()
self.bridge_manager.ensure_async()
deadline = time.time() + self.startup_timeout_s
while time.time() < deadline:
@@ -88,7 +90,7 @@ class ZcanBridgeSeatExecutor:
return value
time.sleep(0.1)
qualifier = "及座椅ECU协议" if require_protocol else ""
raise RuntimeError(f"ZCANPRO桥接{qualifier}未在线")
raise RuntimeError(f"共享CAN Runtime{qualifier}未在线")
def _payload(
self,
@@ -119,25 +121,62 @@ class ZcanBridgeSeatExecutor:
status = self._wait_online(require_protocol=True)
if status.get("current_request_id"):
raise RuntimeError(
"ZCANPRO桥接正在执行其他台架任务:%s" %
"共享CAN Runtime正在执行其他台架任务:%s" %
status.get("current_request_id")
)
if (
hasattr(self.bridge_manager, "seat_position_sync_required")
and self.bridge_manager.seat_position_sync_required()
):
sync_id = "SEAT_SYNC_%d_%s" % (
int(time.time() * 1000), uuid.uuid4().hex[:6]
)
sync_result = self._submit_request(
sync_id,
action="initialize_known_ranges",
payload={"force_all_axes": True, "home_nonzero_axes": False},
timeout_s=self.request_timeout_s,
)
if sync_result.get("result") != "PASS":
detail = sync_result.get("details") or {}
raise RuntimeError(
detail.get("summary")
or sync_result.get("summary")
or "四轴位置同步失败,未执行座椅请求"
)
self.bridge_manager.mark_seat_position_synchronized()
request_id = "SEAT_ORCH_%d_%s" % (
int(time.time() * 1000), uuid.uuid4().hex[:6]
)
return self._submit_request(
request_id,
action="execute_command_plan",
payload=payload,
timeout_s=self.request_timeout_s,
)
def _submit_request(
self,
request_id: str,
*,
action: str,
payload: dict[str, Any],
timeout_s: float,
) -> dict[str, Any]:
request = {
"schema": "seat-function-request-v1",
"request_id": request_id,
"created_epoch": time.time(),
"armed_until_epoch": time.time() + self.request_timeout_s + 30.0,
"armed_until_epoch": time.time() + timeout_s + 30.0,
"source": "cabin_cognitive_orchestrator",
"action": "execute_command_plan",
"action": action,
"payload": payload,
}
_atomic_json(self.pending_dir / f"{request_id}.json", request)
deadline = time.time() + self.request_timeout_s
deadline = time.time() + timeout_s
while time.time() < deadline:
status = self._online_status(require_protocol=False)
if status is None:
@@ -147,7 +186,49 @@ class ZcanBridgeSeatExecutor:
if result.get("request_id") == request_id:
return result
time.sleep(0.1)
raise RuntimeError("ZCANPRO桥接执行座椅请求超时,已停止等待")
raise RuntimeError("共享CAN Runtime执行座椅请求超时,已停止等待")
@staticmethod
def _physical_state_from_detail(detail: dict[str, Any]) -> dict[str, Any] | None:
motors = detail.get("motors") or []
axes = ("longitudinal", "cushion_tilt", "backrest_recline", "legrest_height")
positions: dict[str, Any] = {}
missing: list[str] = []
for motor, axis in enumerate(axes):
item = motors[motor] if motor < len(motors) else None
if not isinstance(item, dict):
missing.append(axis)
continue
positions[axis] = {
"position_pct": round(int(item.get("position", 0)) / 100),
"position_valid": bool(item.get("position_valid")),
"motion_state": "moving" if item.get("active") else "stopped",
}
thermal = detail.get("thermal") or []
if not positions and not thermal:
return None
thermal_byte = int(thermal[0]) if thermal else 0
ventilation_raw = thermal_byte & 0x07
heating_raw = (thermal_byte >> 3) & 0x07
return {
"position": positions,
"ventilation": {
"state": "off" if ventilation_raw <= 1 else "on",
"level": max(0, ventilation_raw - 1),
},
"heating": {
"state": "off" if heating_raw <= 1 else "on",
"level": max(0, heating_raw - 1),
},
"captured_at": _now(),
"source": "physical_can",
"channel": 0,
"target": {
"device": "seat", "zone": "front_right", "instance": "passenger"
},
"complete": not missing and bool(thermal),
"missing": missing + ([] if thermal else ["thermal"]),
}
@staticmethod
def _report(
@@ -159,10 +240,15 @@ class ZcanBridgeSeatExecutor:
request_id = function_config["metadata"]["request_id"]
passed = error is None and (bridge_result or {}).get("result") == "PASS"
detail = (bridge_result or {}).get("details") or {}
physical_state = (
ZcanBridgeSeatExecutor._physical_state_from_detail(detail)
if passed
else None
)
message = (
detail.get("summary")
or (bridge_result or {}).get("summary")
or (str(error) if error is not None else "ZCANPRO桥接未返回通过结果")
or (str(error) if error is not None else "共享CAN Runtime未返回通过结果")
)
results = [
{
@@ -171,9 +257,14 @@ class ZcanBridgeSeatExecutor:
"status": "succeeded" if passed else "failed",
"message": message,
"telemetry": {
"transport": "zcanpro_shared_bridge",
"transport": "shared_can_runtime",
"channel": 0,
"hardware_feedback": passed,
"simulated": False,
"feedback_required": True,
"feedback_observed": physical_state is not None,
"feedback_match": physical_state is not None,
"physical_state": physical_state,
"bridge_result": bridge_result,
"error_type": type(error).__name__ if error is not None else None,
"stop_all_attempted": bool(detail.get("stop_all_attempted")),
@@ -181,23 +272,26 @@ class ZcanBridgeSeatExecutor:
}
for operation in function_config["spec"]["operations"]
]
spec = {
"mode": "hil",
"status": "succeeded" if passed else "failed",
"started_at": started_at,
"finished_at": _now(),
"results": results,
}
if physical_state is not None:
spec["physical_state"] = physical_state
return {
"apiVersion": "cognitive-agent/v1alpha1",
"kind": "ExecutionReport",
"metadata": {
"id": f"report-{request_id}",
"name": "T2G seat ZCANPRO bridge execution report",
"name": "T2G seat shared CAN runtime execution report",
"version": "1.0.0",
"request_id": request_id,
"labels": {"simulation": "false", "feedback": "physical_can"},
},
"spec": {
"mode": "hil",
"status": "succeeded" if passed else "failed",
"started_at": started_at,
"finished_at": _now(),
"results": results,
},
"spec": spec,
}
def execute(