This commit is contained in:
orin
2026-07-30 08:45:38 +08:00
commit 25b24ac4cd
1333 changed files with 170092 additions and 0 deletions
+391
View File
@@ -0,0 +1,391 @@
"""Configuration and fail-closed gates for the T2G seat hardware bench."""
from __future__ import annotations
import hashlib
import hmac
import os
from dataclasses import dataclass
from pathlib import Path
from typing import Any, Callable
from cognitive_agent.infrastructure.firmware import (
EXPECTED_IDENTITY_PAYLOAD,
FirmwareProgrammingError,
T2GFirmwareGuard,
T2GSeatProgrammer,
)
from .gateway import CanBus, CanGatewayError, PythonCanBus, ZlgCanBridgeBus
TRUE_VALUES = {"1", "true", "yes", "on"}
VALID_PROFILES = {"combined"}
SHARED_PHYSICAL_TARGETS = {
("front_left", "driver"),
("front_right", "passenger"),
}
CAPABILITY_PROFILE = {
"seat.set_heating": "combined",
"seat.set_ventilation": "combined",
"seat.set_longitudinal_position": "combined",
"seat.set_backrest_recline": "combined",
"seat.set_legrest_height": "combined",
"seat.set_legrest_extension": "combined",
}
class HardwareSafetyError(ValueError):
"""Raised before any CAN device is opened when a safety gate fails."""
def _env_bool(name: str, default: bool = False) -> bool:
value = os.getenv(name)
return default if value is None else value.strip().lower() in TRUE_VALUES
def _env_int(name: str, default: int) -> int:
value = os.getenv(name)
if value is None or not value.strip():
return default
try:
return int(value, 0)
except ValueError as exc:
raise HardwareSafetyError(f"{name} must be an integer") from exc
def _profiles_from_env() -> frozenset[str]:
raw = os.getenv(
"COGNITIVE_AGENT_SEAT_FIRMWARE_PROFILES",
os.getenv("COGNITIVE_AGENT_SEAT_FIRMWARE_PROFILE", ""),
)
profiles = frozenset(
item.strip().lower() for item in raw.replace(";", ",").split(",") if item.strip()
)
invalid = profiles - VALID_PROFILES
if invalid:
raise HardwareSafetyError(
"unknown seat firmware profile(s): " + ", ".join(sorted(invalid))
)
return profiles
@dataclass(frozen=True)
class SeatHardwareSettings:
"""Environment-owned hardware configuration; secrets are never made public."""
repository_root: Path
enabled: bool
approval_token: str
profiles: frozenset[str]
backend: str
bitrate: int
channel: str
interface: str
device_type: int
device_index: int
zlg_python: Path
zlg_dll: Path
bridge_script: Path
sdk_root: Path
cspybat: Path
auto_flash_enabled: bool
firmware_probe_timeout_ms: int
firmware_boot_wait_ms: int
firmware_program_timeout_ms: int
allow_unverified_longitudinal: bool
thermal_timeout_ms: int
ack_timeout_ms: int
firmware_images: dict[str, Path]
firmware_debug_files: dict[str, Path]
firmware_macro_files: dict[str, Path]
@classmethod
def from_env(cls, repository_root: Path) -> "SeatHardwareSettings":
sdk_root = Path(
os.getenv(
"COGNITIVE_AGENT_T2G_SDK_ROOT",
r"C:\Users\D30042075206\Desktop\T2G_Sample_Driver_Library_7.9.0",
)
).expanduser()
backend = os.getenv("COGNITIVE_AGENT_SEAT_CAN_BACKEND", "zlgcan").strip().lower()
if backend not in {"zlgcan", "python-can"}:
raise HardwareSafetyError(
"COGNITIVE_AGENT_SEAT_CAN_BACKEND must be zlgcan or python-can"
)
firmware_root = (
repository_root / "firmware" / "t2g" / "Seat_Controller_demo"
)
images = {
"combined": firmware_root
/ "IAR"
/ "rev_d"
/ "Exe"
/ "Seat_Controller_demo.hex",
}
debug_files = {
"combined": firmware_root
/ "IAR"
/ "rev_d"
/ "Exe"
/ "Seat_Controller_demo.out",
}
macro_files = {
"combined": firmware_root / "IAR" / "Seat_Controller_program.mac",
}
return cls(
repository_root=repository_root,
enabled=_env_bool("COGNITIVE_AGENT_HARDWARE_EXECUTION_ENABLED"),
approval_token=os.getenv("COGNITIVE_AGENT_HARDWARE_APPROVAL_TOKEN", ""),
profiles=_profiles_from_env(),
backend=backend,
bitrate=_env_int("COGNITIVE_AGENT_SEAT_CAN_BITRATE", 500_000),
channel=os.getenv("COGNITIVE_AGENT_SEAT_CAN_CHANNEL", "0").strip(),
interface=os.getenv("COGNITIVE_AGENT_SEAT_CAN_INTERFACE", "socketcan").strip(),
device_type=_env_int("COGNITIVE_AGENT_ZLGCAN_DEVICE_TYPE", 41),
device_index=_env_int("COGNITIVE_AGENT_ZLGCAN_DEVICE_INDEX", 0),
zlg_python=Path(
os.getenv(
"COGNITIVE_AGENT_ZLGCAN_PYTHON",
r"C:\Program Files (x86)\ZCANPRO\python38\python.exe",
)
),
zlg_dll=Path(
os.getenv(
"COGNITIVE_AGENT_ZLGCAN_DLL",
r"C:\Program Files (x86)\ZCANPRO\zlgcan.dll",
)
),
bridge_script=repository_root
/ "src"
/ "cognitive_agent"
/ "infrastructure"
/ "can"
/ "zlgcan_bridge.py",
sdk_root=sdk_root,
cspybat=Path(
os.getenv(
"COGNITIVE_AGENT_IAR_CSPYBAT",
r"D:\Program Files\IAR Systems\Embedded Workbench 9.2\common\bin\cspybat.exe",
)
),
auto_flash_enabled=_env_bool("COGNITIVE_AGENT_SEAT_AUTO_FLASH_ENABLED"),
firmware_probe_timeout_ms=_env_int(
"COGNITIVE_AGENT_SEAT_FIRMWARE_PROBE_TIMEOUT_MS", 750
),
firmware_boot_wait_ms=_env_int(
"COGNITIVE_AGENT_SEAT_FIRMWARE_BOOT_WAIT_MS", 1_500
),
firmware_program_timeout_ms=_env_int(
"COGNITIVE_AGENT_SEAT_FIRMWARE_PROGRAM_TIMEOUT_MS", 120_000
),
allow_unverified_longitudinal=_env_bool(
"COGNITIVE_AGENT_ENABLE_UNVERIFIED_LONGITUDINAL"
),
thermal_timeout_ms=_env_int(
"COGNITIVE_AGENT_SEAT_THERMAL_TIMEOUT_MS", 2_000
),
ack_timeout_ms=_env_int("COGNITIVE_AGENT_SEAT_ACK_TIMEOUT_MS", 1_500),
firmware_images=images,
firmware_debug_files=debug_files,
firmware_macro_files=macro_files,
)
@property
def adapter_configured(self) -> bool:
if self.bitrate != 500_000:
return False
if self.backend == "zlgcan":
return (
self.zlg_python.is_file()
and self.zlg_dll.is_file()
and self.bridge_script.is_file()
)
return bool(self.interface and self.channel)
@property
def configured(self) -> bool:
return bool(
self.enabled
and self.approval_token
and self.profiles
and self.adapter_configured
and all(self.firmware_images[item].is_file() for item in self.profiles)
and (not self.auto_flash_enabled or self.programmer_configured)
)
@property
def programmer_configured(self) -> bool:
if not self.profiles:
return False
try:
return all(self._programmer(item).configured for item in self.profiles)
except (FirmwareProgrammingError, OSError):
return False
def authorize(self, supplied_token: str | None) -> None:
if not self.enabled:
raise HardwareSafetyError(
"hardware execution is disabled; set COGNITIVE_AGENT_HARDWARE_EXECUTION_ENABLED=true"
)
if not self.approval_token:
raise HardwareSafetyError(
"COGNITIVE_AGENT_HARDWARE_APPROVAL_TOKEN is not configured"
)
if not supplied_token or not hmac.compare_digest(
supplied_token, self.approval_token
):
raise HardwareSafetyError("hardware approval token is invalid")
if not self.profiles:
raise HardwareSafetyError(
"the combined IAR seat-controller firmware profile is not selected"
)
if self.bitrate != 500_000:
raise HardwareSafetyError("the verified T2G seat protocol requires 500000 bit/s")
if not self.adapter_configured:
raise HardwareSafetyError("the configured CAN adapter runtime is not ready")
missing = [
str(self.firmware_images[item])
for item in self.profiles
if not self.firmware_images[item].is_file()
]
if missing:
raise HardwareSafetyError(
"selected IAR firmware image is missing: " + ", ".join(missing)
)
if self.auto_flash_enabled and not self.programmer_configured:
raise HardwareSafetyError(
"automatic seat-firmware programming is enabled but IAR C-SPY, "
"the repository OUT image, or the C-SPY macro is unavailable"
)
def validate_function_config(self, function_config: dict[str, Any]) -> None:
operations = function_config.get("spec", {}).get("operations", [])
if not operations:
raise HardwareSafetyError("hardware execution requires at least one seat operation")
physical_requests: dict[str, dict[str, Any]] = {}
for operation in operations:
capability = str(operation.get("capability", ""))
target = operation.get("target", {})
target_key = (str(target.get("zone", "")), str(target.get("instance", "")))
if target_key not in SHARED_PHYSICAL_TARGETS:
raise HardwareSafetyError(
"the T2G bench accepts driver/front_left and passenger/front_right; "
"both aliases operate the same physical seat actuators"
)
try:
profile = CAPABILITY_PROFILE[capability]
except KeyError as exc:
raise HardwareSafetyError(
f"the T2G IAR firmware does not implement {capability}"
) from exc
if profile not in self.profiles:
raise HardwareSafetyError(
f"{capability} requires the '{profile}' firmware profile"
)
parameters = dict(operation.get("parameters", {}))
previous = physical_requests.get(capability)
if previous is not None and previous != parameters:
raise HardwareSafetyError(
f"driver and passenger requests conflict for shared physical capability {capability}"
)
physical_requests[capability] = parameters
if (
capability == "seat.set_longitudinal_position"
and not self.allow_unverified_longitudinal
):
raise HardwareSafetyError(
"longitudinal Motor3 is unwired/unverified; explicitly set "
"COGNITIVE_AGENT_ENABLE_UNVERIFIED_LONGITUDINAL=true only after calibration"
)
def bus_factory(self) -> Callable[[], CanBus]:
if self.backend == "zlgcan":
try:
numeric_channel = int(self.channel, 0)
except ValueError as exc:
raise CanGatewayError(
"ZLG CAN channel must be an integer such as 0"
) from exc
def create_zlg_bus() -> CanBus:
return ZlgCanBridgeBus(
python_executable=self.zlg_python,
bridge_script=self.bridge_script,
dll_path=self.zlg_dll,
device_type=self.device_type,
device_index=self.device_index,
channel=numeric_channel,
bitrate=self.bitrate,
)
return create_zlg_bus
def create_python_can_bus() -> CanBus:
return PythonCanBus(
interface=self.interface,
channel=self.channel,
bitrate=self.bitrate,
)
return create_python_can_bus
def _programmer(self, profile: str = "combined") -> T2GSeatProgrammer:
return T2GSeatProgrammer(
cspybat=self.cspybat,
debug_file=self.firmware_debug_files[profile],
hex_image=self.firmware_images[profile],
macro_file=self.firmware_macro_files[profile],
timeout_ms=self.firmware_program_timeout_ms,
)
def firmware_guard(self) -> T2GFirmwareGuard:
return T2GFirmwareGuard(
self.bus_factory(),
self._programmer() if self.auto_flash_enabled else None,
auto_flash=self.auto_flash_enabled,
probe_timeout_ms=self.firmware_probe_timeout_ms,
boot_wait_ms=self.firmware_boot_wait_ms,
)
def public_info(self) -> dict[str, Any]:
images = {}
for profile, path in self.firmware_images.items():
exists = path.is_file()
images[profile] = {
"exists": exists,
"filename": path.name,
"sha256": (
hashlib.sha256(path.read_bytes()).hexdigest() if exists else None
),
}
return {
"enabled": self.enabled,
"configured": self.configured,
"approval_required": True,
"approval_configured": bool(self.approval_token),
"platform": "t2g_seat_bench",
"backend": self.backend,
"adapter_configured": self.adapter_configured,
"channel": self.channel,
"bitrate": self.bitrate,
"frame_format": "classic_can_11_bit",
"firmware_profiles": sorted(self.profiles),
"firmware_images": images,
"firmware_status": "identity_verified_before_each_hardware_request",
"firmware_identity": EXPECTED_IDENTITY_PAYLOAD.hex(" ").upper(),
"firmware_source": "repository",
"auto_flash_enabled": self.auto_flash_enabled,
"programmer_configured": self.programmer_configured,
"programmer_backend": "iar_cspy_cmsisdap",
"supported_targets": ["front_left", "front_right"],
"physical_target": "t2g_primary_seat",
"target_aliasing": "driver_and_passenger_share_one_physical_actuator_group",
"unverified_longitudinal_enabled": self.allow_unverified_longitudinal,
"limitations": [
"hardware execution requires the unified Seat_Controller_demo image on one development board",
"driver and passenger commands are aliases for the same physical motor/heater/fan group",
"Motor3 longitudinal motion stays blocked until explicitly enabled after wiring and calibration",
"cushion tilt has no physical actuator in Seat_Move_demo",
],
}