feat: integrate cabin CAN and verified dynamic seat modes

This commit is contained in:
vee
2026-08-04 17:51:38 +08:00
parent 155fbb59ba
commit dc7c91800e
99 changed files with 51897 additions and 3618 deletions
@@ -19,7 +19,14 @@ from cognitive_agent.infrastructure.firmware import (
T2GSeatProgrammer,
)
from .gateway import CanBus, CanGatewayError, PythonCanBus, ZlgCanBridgeBus
from .gateway import (
CanBus,
CanGatewayError,
PythonCanBus,
ZlgCanBridgeBus,
ZlgCanBridgeProcess,
ZlgChannelConfig,
)
TRUE_VALUES = {"1", "true", "yes", "on"}
@@ -32,6 +39,8 @@ CAPABILITY_PROFILE = {
"seat.set_cushion_tilt": "combined",
"seat.set_backrest_recline": "combined",
"seat.set_legrest_height": "combined",
"seat.start_dynamic_mode": "combined",
"seat.stop_dynamic_mode": "combined",
}
@@ -132,6 +141,7 @@ class SeatHardwareSettings:
profiles: frozenset[str]
backend: str
bitrate: int
termination_enabled: bool
channel: str
interface: str
device_type: int
@@ -196,6 +206,9 @@ class SeatHardwareSettings:
profiles=_profiles_from_env(),
backend=backend,
bitrate=_env_int("COGNITIVE_AGENT_SEAT_CAN_BITRATE", 500_000),
termination_enabled=_env_bool(
"COGNITIVE_AGENT_SEAT_CAN_TERMINATION_ENABLED", default=True
),
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),
@@ -234,7 +247,7 @@ class SeatHardwareSettings:
"COGNITIVE_AGENT_SEAT_HOME_TIMEOUT_S", 120
),
firmware_probe_timeout_ms=_env_int(
"COGNITIVE_AGENT_SEAT_FIRMWARE_PROBE_TIMEOUT_MS", 1_200
"COGNITIVE_AGENT_SEAT_FIRMWARE_PROBE_TIMEOUT_MS", 3_500
),
firmware_boot_wait_ms=_env_int(
"COGNITIVE_AGENT_SEAT_FIRMWARE_BOOT_WAIT_MS", 1_500
@@ -368,6 +381,7 @@ class SeatHardwareSettings:
device_index=self.device_index,
channel=numeric_channel,
bitrate=self.bitrate,
termination_enabled=self.termination_enabled,
)
return create_zlg_bus
@@ -390,9 +404,11 @@ class SeatHardwareSettings:
timeout_ms=self.firmware_program_timeout_ms,
)
def firmware_guard(self) -> T2GFirmwareGuard:
def firmware_guard(
self, bus_factory: Callable[[], CanBus] | None = None
) -> T2GFirmwareGuard:
return T2GFirmwareGuard(
self.bus_factory(),
bus_factory or 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,
@@ -420,6 +436,7 @@ class SeatHardwareSettings:
"adapter_configured": self.adapter_configured,
"channel": self.channel,
"bitrate": self.bitrate,
"termination_enabled": self.termination_enabled,
"frame_format": "classic_can_11_bit",
"firmware_profiles": sorted(self.profiles),
"firmware_images": images,
@@ -450,3 +467,209 @@ class SeatHardwareSettings:
"current diagnostics use conservative bench thresholds and do not replace external power limiting",
],
}
@dataclass(frozen=True)
class VehicleCanSettings:
"""Fail-closed channel/timing contract imported from the verified CAN_DEV branch."""
repository_root: Path
enabled: bool
approval_required: bool
approval_token: str
backend: str
channel: int
arbitration_bitrate: int
data_bitrate: int
termination_enabled: bool
clock_hz: int
arbitration_tseg1: int
arbitration_tseg2: int
arbitration_sjw: int
arbitration_brp: int
data_tseg1: int
data_tseg2: int
data_sjw: int
data_brp: int
feedback_timeout_ms: int
device_type: int
device_index: int
zlg_python: Path
zlg_dll: Path
bridge_script: Path
@classmethod
def from_env(cls, repository_root: Path) -> "VehicleCanSettings":
return cls(
repository_root=repository_root,
enabled=_env_bool(
"COGNITIVE_AGENT_VEHICLE_CAN_ENABLED",
default=_env_bool("COGNITIVE_AGENT_HARDWARE_EXECUTION_ENABLED"),
),
approval_required=_env_bool(
"COGNITIVE_AGENT_HARDWARE_APPROVAL_TOKEN_REQUIRED", default=True
),
approval_token=os.getenv("COGNITIVE_AGENT_HARDWARE_APPROVAL_TOKEN", ""),
backend=os.getenv(
"COGNITIVE_AGENT_VEHICLE_CAN_BACKEND", "zlgcan"
).strip().lower(),
channel=_env_int("COGNITIVE_AGENT_VEHICLE_CAN_CHANNEL", 1),
arbitration_bitrate=_env_int(
"COGNITIVE_AGENT_VEHICLE_CAN_ARBITRATION_BITRATE", 500_000
),
data_bitrate=_env_int(
"COGNITIVE_AGENT_VEHICLE_CAN_DATA_BITRATE", 2_000_000
),
termination_enabled=_env_bool(
"COGNITIVE_AGENT_VEHICLE_CAN_TERMINATION_ENABLED", default=False
),
clock_hz=60_000_000,
arbitration_tseg1=14,
arbitration_tseg2=3,
arbitration_sjw=2,
arbitration_brp=5,
data_tseg1=10,
data_tseg2=2,
data_sjw=2,
data_brp=1,
feedback_timeout_ms=_env_int(
"COGNITIVE_AGENT_VEHICLE_CAN_FEEDBACK_TIMEOUT_MS", 1_500
),
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",
)
@property
def adapter_configured(self) -> bool:
return bool(
self.backend == "zlgcan"
and self.zlg_python.is_file()
and self.zlg_dll.is_file()
and self.bridge_script.is_file()
)
@property
def configured(self) -> bool:
return bool(
self.enabled
and (not self.approval_required or self.approval_token)
and self.adapter_configured
and self.timing_is_verified
)
@property
def timing_is_verified(self) -> bool:
return (
self.arbitration_bitrate == 500_000
and self.data_bitrate == 2_000_000
and 60_000_000
// ((self.arbitration_brp + 1) * (self.arbitration_tseg1 + self.arbitration_tseg2 + 3))
== 500_000
and 60_000_000
// ((self.data_brp + 1) * (self.data_tseg1 + self.data_tseg2 + 3))
== 2_000_000
)
def authorize(self, supplied_token: str | None) -> None:
if not self.enabled:
raise HardwareSafetyError(
"vehicle CAN execution is disabled; set COGNITIVE_AGENT_VEHICLE_CAN_ENABLED=true"
)
if self.approval_required:
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 self.backend != "zlgcan":
raise HardwareSafetyError(
"vehicle CAN FD currently requires the zlgcan multi-channel backend"
)
if self.channel < 0:
raise HardwareSafetyError("vehicle CAN channel cannot be negative")
if not self.timing_is_verified:
raise HardwareSafetyError(
"vehicle CAN timing must remain ISO CAN FD 500 kbit/s / 2 Mbit/s at 80% sample point"
)
if not 100 <= self.feedback_timeout_ms <= 10_000:
raise HardwareSafetyError(
"COGNITIVE_AGENT_VEHICLE_CAN_FEEDBACK_TIMEOUT_MS must be between 100 and 10000"
)
if not self.adapter_configured:
raise HardwareSafetyError("the configured ZLG CAN FD runtime is not ready")
def channel_config(self) -> ZlgChannelConfig:
return ZlgChannelConfig(
channel=self.channel,
arbitration_bitrate=self.arbitration_bitrate,
data_bitrate=self.data_bitrate,
can_fd_iso=True,
termination_enabled=self.termination_enabled,
)
def bridge(self, channels: list[ZlgChannelConfig]) -> ZlgCanBridgeProcess:
return ZlgCanBridgeProcess(
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,
channels=channels,
)
def public_info(self) -> dict[str, Any]:
return {
"enabled": self.enabled,
"configured": self.configured,
"backend": self.backend,
"platform": "t2g_cabin_bench",
"channel": self.channel,
"mode": 0,
"work_mode": "normal",
"can_fd_standard": "ISO",
"termination_enabled": self.termination_enabled,
"clock_hz": self.clock_hz,
"arbitration": {
"bitrate": self.arbitration_bitrate,
"tseg1": self.arbitration_tseg1,
"tseg2": self.arbitration_tseg2,
"sjw": self.arbitration_sjw,
"smp": 0,
"brp": self.arbitration_brp,
"total_tq": 20,
"sample_point_percent": 80,
},
"data": {
"bitrate": self.data_bitrate,
"tseg1": self.data_tseg1,
"tseg2": self.data_tseg2,
"sjw": self.data_sjw,
"smp": 0,
"brp": self.data_brp,
"total_tq": 15,
"sample_point_percent": 80,
},
"source_branch": "project-code/CAN_DEV",
"source_commit": "6e7ed04a468dfcde7efda9e30bc15144bf02faf9",
}