feat: integrate cabin CAN and verified dynamic seat modes
This commit is contained in:
@@ -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",
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user