"""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 and orchestration therefore exchange bounded, armed requests with one transport-neutral runtime instead of opening the adapter independently. """ from __future__ import annotations from datetime import UTC, datetime import json import os from pathlib import Path import time from typing import Any import uuid def _now() -> str: return datetime.now(UTC).isoformat().replace("+00:00", "Z") def _read_json(path: Path) -> dict[str, Any] | None: try: value = json.loads(path.read_text(encoding="utf-8")) except (OSError, ValueError, TypeError): return None return value if isinstance(value, dict) else None def _atomic_json(path: Path, value: dict[str, Any]) -> None: path.parent.mkdir(parents=True, exist_ok=True) temporary = path.with_name( f".{path.name}.{os.getpid()}.{time.time_ns()}.tmp" ) temporary.write_text( json.dumps(value, ensure_ascii=False, indent=2), encoding="utf-8" ) os.replace(temporary, path) class ZcanBridgeSeatExecutor: """Hardware-seat executor compatible with ``HardwareSeatExecutor``.""" def __init__( self, matrix: Any, bridge_manager: Any, status_path: Path, pending_dir: Path, *, required_version: str, auto_home: bool, startup_timeout_s: float = 12.0, request_timeout_s: float = 660.0, ) -> None: self.matrix = matrix self.bridge_manager = bridge_manager self.status_path = Path(status_path) self.pending_dir = Path(pending_dir) self.required_version = required_version self.auto_home = bool(auto_home) self.startup_timeout_s = float(startup_timeout_s) self.request_timeout_s = float(request_timeout_s) def _online_status(self, *, require_protocol: bool) -> dict[str, Any] | None: value = _read_json(self.status_path) if value is None or value.get("bridge_version") != self.required_version: return None try: fresh = time.time() - float(value.get("heartbeat_epoch", 0)) < 3.0 except (TypeError, ValueError): fresh = False if not fresh: return None if require_protocol and not (value.get("system") and value.get("frame_epochs")): return None 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: value = self._online_status(require_protocol=require_protocol) if value is not None: return value time.sleep(0.1) qualifier = "及座椅ECU协议" if require_protocol else "" raise RuntimeError(f"共享CAN Runtime{qualifier}未在线") def _payload( self, command_plan: dict[str, Any], function_config: dict[str, Any], ) -> dict[str, Any]: commands = [] for command in command_plan["spec"]["commands"]: encoded = self.matrix.encode(command["message"], command["signals"]) item = dict(command) item["frame_id"] = int(encoded["frame_id"], 16) item["data"] = list(bytes.fromhex(encoded["data_hex"])) commands.append(item) return { "name": command_plan.get("metadata", {}).get( "name", "自然语言座椅控制" ), "commands": commands, "operations": list(function_config["spec"]["operations"]), "allow_auto_home": self.auto_home, } def _submit(self, payload: dict[str, Any]) -> dict[str, Any]: status = self._wait_online(require_protocol=True) idle_deadline = time.time() + self.startup_timeout_s while status.get("current_request_id") and time.time() < idle_deadline: time.sleep(0.1) status = self._wait_online(require_protocol=True) if status.get("current_request_id"): raise RuntimeError( "共享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() + timeout_s + 30.0, "source": "cabin_cognitive_orchestrator", "action": action, "payload": payload, } _atomic_json(self.pending_dir / f"{request_id}.json", request) deadline = time.time() + timeout_s while time.time() < deadline: status = self._online_status(require_protocol=False) if status is None: time.sleep(0.1) continue result = status.get("last_result") or {} if result.get("request_id") == request_id: return result time.sleep(0.1) 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( function_config: dict[str, Any], started_at: str, bridge_result: dict[str, Any] | None, error: Exception | None, ) -> dict[str, Any]: 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 "共享CAN Runtime未返回通过结果") ) results = [ { "ref": operation["id"], "domain": "seat", "status": "succeeded" if passed else "failed", "message": message, "telemetry": { "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")), }, } 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 shared CAN runtime execution report", "version": "1.0.0", "request_id": request_id, "labels": {"simulation": "false", "feedback": "physical_can"}, }, "spec": spec, } def execute( self, command_plan: dict[str, Any], function_config: dict[str, Any], actuator_state: dict[str, Any] | None = None, ) -> dict[str, Any]: del actuator_state started_at = _now() try: result = self._submit(self._payload(command_plan, function_config)) except Exception as exc: # normalize bridge/startup failures for the API return self._report(function_config, started_at, None, exc) return self._report(function_config, started_at, result, None) def read_physical_state(self) -> dict[str, Any]: status = self._wait_online(require_protocol=True) axes = ("longitudinal", "cushion_tilt", "backrest_recline", "legrest_height") positions: dict[str, Any] = {} missing = [] motors = status.get("motors") or [] 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 = status.get("thermal") or [] 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", "target": { "device": "seat", "zone": "front_right", "instance": "passenger" }, "complete": not missing, "missing": missing, }