添加无线模块
This commit is contained in:
@@ -4,7 +4,9 @@ from __future__ import annotations
|
||||
|
||||
import hashlib
|
||||
import hmac
|
||||
import importlib.util
|
||||
import os
|
||||
import sys
|
||||
import threading
|
||||
import time
|
||||
from contextlib import contextmanager
|
||||
@@ -114,6 +116,13 @@ def _env_int(name: str, default: int) -> int:
|
||||
raise HardwareSafetyError(f"{name} must be an integer") from exc
|
||||
|
||||
|
||||
def _module_available(name: str) -> bool:
|
||||
try:
|
||||
return importlib.util.find_spec(name) is not None
|
||||
except ModuleNotFoundError:
|
||||
return False
|
||||
|
||||
|
||||
def _profiles_from_env() -> frozenset[str]:
|
||||
raw = os.getenv(
|
||||
"COGNITIVE_AGENT_SEAT_FIRMWARE_PROFILES",
|
||||
@@ -405,12 +414,19 @@ class SeatHardwareSettings:
|
||||
)
|
||||
|
||||
def firmware_guard(
|
||||
self, bus_factory: Callable[[], CanBus] | None = None
|
||||
self,
|
||||
bus_factory: Callable[[], CanBus] | None = None,
|
||||
*,
|
||||
allow_programming: bool = True,
|
||||
) -> T2GFirmwareGuard:
|
||||
return T2GFirmwareGuard(
|
||||
bus_factory or self.bus_factory(),
|
||||
self._programmer() if self.auto_flash_enabled else None,
|
||||
auto_flash=self.auto_flash_enabled,
|
||||
(
|
||||
self._programmer()
|
||||
if self.auto_flash_enabled and allow_programming
|
||||
else None
|
||||
),
|
||||
auto_flash=self.auto_flash_enabled and allow_programming,
|
||||
probe_timeout_ms=self.firmware_probe_timeout_ms,
|
||||
boot_wait_ms=self.firmware_boot_wait_ms,
|
||||
)
|
||||
@@ -500,6 +516,52 @@ class VehicleCanSettings:
|
||||
|
||||
@classmethod
|
||||
def from_env(cls, repository_root: Path) -> "VehicleCanSettings":
|
||||
backend = os.getenv(
|
||||
"COGNITIVE_AGENT_VEHICLE_CAN_BACKEND", "zlgcan"
|
||||
).strip().lower()
|
||||
linux_vci = backend == "zlgcan-linux"
|
||||
default_linux_library = (
|
||||
repository_root.parent / "project-code" / "canfd" / "libusbcanfd.so"
|
||||
)
|
||||
if linux_vci:
|
||||
zlg_python = Path(
|
||||
os.getenv("COGNITIVE_AGENT_ZLGCAN_PYTHON", sys.executable)
|
||||
)
|
||||
zlg_library = Path(
|
||||
os.getenv(
|
||||
"COGNITIVE_AGENT_ZLGCAN_LINUX_LIBRARY",
|
||||
str(default_linux_library),
|
||||
)
|
||||
)
|
||||
bridge_script = (
|
||||
repository_root
|
||||
/ "src"
|
||||
/ "cognitive_agent"
|
||||
/ "infrastructure"
|
||||
/ "can"
|
||||
/ "zlgcan_linux_bridge.py"
|
||||
)
|
||||
else:
|
||||
zlg_python = Path(
|
||||
os.getenv(
|
||||
"COGNITIVE_AGENT_ZLGCAN_PYTHON",
|
||||
r"C:\Program Files (x86)\ZCANPRO\python38\python.exe",
|
||||
)
|
||||
)
|
||||
zlg_library = 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"
|
||||
)
|
||||
return cls(
|
||||
repository_root=repository_root,
|
||||
enabled=_env_bool(
|
||||
@@ -514,9 +576,7 @@ class VehicleCanSettings:
|
||||
"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(),
|
||||
backend=backend,
|
||||
channel=_env_int("COGNITIVE_AGENT_VEHICLE_CAN_CHANNEL", 1),
|
||||
arbitration_bitrate=_env_int(
|
||||
"COGNITIVE_AGENT_VEHICLE_CAN_ARBITRATION_BITRATE", 500_000
|
||||
@@ -539,47 +599,42 @@ class VehicleCanSettings:
|
||||
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_type=_env_int(
|
||||
"COGNITIVE_AGENT_ZLGCAN_DEVICE_TYPE", 33 if linux_vci else 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",
|
||||
zlg_python=zlg_python,
|
||||
zlg_dll=zlg_library,
|
||||
bridge_script=bridge_script,
|
||||
)
|
||||
|
||||
@property
|
||||
def adapter_configured(self) -> bool:
|
||||
return bool(
|
||||
self.backend == "zlgcan"
|
||||
self.backend in {"zlgcan", "zlgcan-linux"}
|
||||
and self.zlg_python.is_file()
|
||||
and self.zlg_dll.is_file()
|
||||
and self.bridge_script.is_file()
|
||||
)
|
||||
|
||||
@property
|
||||
def configured(self) -> bool:
|
||||
def state_configured(self) -> bool:
|
||||
"""Whether authoritative read-only CH1 synchronization can run."""
|
||||
|
||||
return bool(
|
||||
self.enabled
|
||||
and (not self.approval_required or self.approval_token)
|
||||
and self.adapter_configured
|
||||
and self.timing_is_verified
|
||||
and self.channel_is_allowed
|
||||
)
|
||||
|
||||
@property
|
||||
def configured(self) -> bool:
|
||||
return bool(
|
||||
self.state_configured
|
||||
and (not self.approval_required or self.approval_token)
|
||||
)
|
||||
|
||||
@property
|
||||
def required_channel(self) -> int:
|
||||
"""Physical body-CAN channel selected by the fail-closed bench policy."""
|
||||
@@ -617,9 +672,9 @@ class VehicleCanSettings:
|
||||
supplied_token, self.approval_token
|
||||
):
|
||||
raise HardwareSafetyError("hardware approval token is invalid")
|
||||
if self.backend != "zlgcan":
|
||||
if self.backend not in {"zlgcan", "zlgcan-linux"}:
|
||||
raise HardwareSafetyError(
|
||||
"vehicle CAN FD currently requires the zlgcan multi-channel backend"
|
||||
"vehicle CAN FD requires the zlgcan or zlgcan-linux backend"
|
||||
)
|
||||
if not self.channel_is_allowed:
|
||||
raise HardwareSafetyError(
|
||||
@@ -648,7 +703,13 @@ class VehicleCanSettings:
|
||||
termination_enabled=self.termination_enabled,
|
||||
)
|
||||
|
||||
def bridge(self, channels: list[ZlgChannelConfig]) -> ZlgCanBridgeProcess:
|
||||
def bridge(
|
||||
self,
|
||||
channels: list[ZlgChannelConfig],
|
||||
*,
|
||||
monitor_only: bool = False,
|
||||
trace_all: bool = False,
|
||||
) -> ZlgCanBridgeProcess:
|
||||
return ZlgCanBridgeProcess(
|
||||
python_executable=self.zlg_python,
|
||||
bridge_script=self.bridge_script,
|
||||
@@ -656,12 +717,15 @@ class VehicleCanSettings:
|
||||
device_type=self.device_type,
|
||||
device_index=self.device_index,
|
||||
channels=channels,
|
||||
monitor_only=monitor_only,
|
||||
trace_all=trace_all,
|
||||
)
|
||||
|
||||
def public_info(self) -> dict[str, Any]:
|
||||
return {
|
||||
"enabled": self.enabled,
|
||||
"configured": self.configured,
|
||||
"state_configured": self.state_configured,
|
||||
"backend": self.backend,
|
||||
"platform": "t2g_cabin_bench",
|
||||
"channel": self.channel,
|
||||
@@ -699,3 +763,125 @@ class VehicleCanSettings:
|
||||
"source_branch": "project-code/CAN_DEV",
|
||||
"source_commit": "6e7ed04a468dfcde7efda9e30bc15144bf02faf9",
|
||||
}
|
||||
|
||||
|
||||
@dataclass(frozen=True)
|
||||
class VehicleMqttSettings:
|
||||
"""Wireless CAN0/CAN1 relay settings with the same physical approval gate."""
|
||||
|
||||
enabled: bool
|
||||
approval_required: bool
|
||||
approval_token: str
|
||||
host: str
|
||||
port: int
|
||||
condition_topic: str
|
||||
control_topic: str
|
||||
qos: int
|
||||
keepalive_s: int
|
||||
username: str | None
|
||||
password: str | None
|
||||
tls_enabled: bool
|
||||
feedback_timeout_ms: int
|
||||
|
||||
@classmethod
|
||||
def from_env(cls) -> "VehicleMqttSettings":
|
||||
username = os.getenv("COGNITIVE_AGENT_VEHICLE_MQTT_USERNAME")
|
||||
return cls(
|
||||
enabled=_env_bool("COGNITIVE_AGENT_VEHICLE_MQTT_ENABLED", default=True),
|
||||
approval_required=_env_bool(
|
||||
"COGNITIVE_AGENT_HARDWARE_APPROVAL_TOKEN_REQUIRED", default=True
|
||||
),
|
||||
approval_token=os.getenv("COGNITIVE_AGENT_HARDWARE_APPROVAL_TOKEN", ""),
|
||||
host=os.getenv("COGNITIVE_AGENT_VEHICLE_MQTT_HOST", "127.0.0.1").strip(),
|
||||
port=_env_int("COGNITIVE_AGENT_VEHICLE_MQTT_PORT", 1883),
|
||||
condition_topic=os.getenv(
|
||||
"COGNITIVE_AGENT_VEHICLE_MQTT_CONDITION_TOPIC",
|
||||
"n7/n7-headunit/can/state",
|
||||
).strip(),
|
||||
control_topic=os.getenv(
|
||||
"COGNITIVE_AGENT_VEHICLE_MQTT_CONTROL_TOPIC",
|
||||
"n7/can_control",
|
||||
).strip(),
|
||||
qos=_env_int("COGNITIVE_AGENT_VEHICLE_MQTT_QOS", 1),
|
||||
keepalive_s=_env_int("COGNITIVE_AGENT_VEHICLE_MQTT_KEEPALIVE_S", 30),
|
||||
username=username.strip() if username and username.strip() else None,
|
||||
password=os.getenv("COGNITIVE_AGENT_VEHICLE_MQTT_PASSWORD"),
|
||||
tls_enabled=_env_bool("COGNITIVE_AGENT_VEHICLE_MQTT_TLS"),
|
||||
feedback_timeout_ms=_env_int(
|
||||
"COGNITIVE_AGENT_VEHICLE_MQTT_FEEDBACK_TIMEOUT_MS", 1_500
|
||||
),
|
||||
)
|
||||
|
||||
@property
|
||||
def adapter_configured(self) -> bool:
|
||||
return bool(
|
||||
self.host
|
||||
and 1 <= self.port <= 65_535
|
||||
and self.condition_topic
|
||||
and self.control_topic
|
||||
and self.condition_topic != self.control_topic
|
||||
and self.qos in {0, 1, 2}
|
||||
and 5 <= self.keepalive_s <= 3_600
|
||||
and 100 <= self.feedback_timeout_ms <= 10_000
|
||||
and _module_available("paho.mqtt.client")
|
||||
)
|
||||
|
||||
@property
|
||||
def state_configured(self) -> bool:
|
||||
return self.enabled and self.adapter_configured
|
||||
|
||||
@property
|
||||
def configured(self) -> bool:
|
||||
return bool(
|
||||
self.state_configured
|
||||
and (not self.approval_required or self.approval_token)
|
||||
)
|
||||
|
||||
def authorize(self, supplied_token: str | None) -> None:
|
||||
if not self.enabled:
|
||||
raise HardwareSafetyError(
|
||||
"vehicle MQTT execution is disabled; set "
|
||||
"COGNITIVE_AGENT_VEHICLE_MQTT_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 not self.adapter_configured:
|
||||
raise HardwareSafetyError("the configured MQTT CAN relay is not ready")
|
||||
|
||||
def public_info(self) -> dict[str, Any]:
|
||||
return {
|
||||
"enabled": self.enabled,
|
||||
"configured": self.configured,
|
||||
"state_configured": self.state_configured,
|
||||
"backend": "mqtt",
|
||||
"host": self.host,
|
||||
"port": self.port,
|
||||
"condition_topic": self.condition_topic,
|
||||
"control_topic": self.control_topic,
|
||||
"qos": self.qos,
|
||||
"tls_enabled": self.tls_enabled,
|
||||
"authentication_configured": bool(self.username),
|
||||
"channels": {"seat": 0, "vehicle": 1},
|
||||
"target_policy": "dual_channel_mqtt_can0_passenger_can1_vehicle",
|
||||
"frame_contract": "n7.can.frame.v1",
|
||||
"can0_seat_protocol": "seat-ota-runtime",
|
||||
"can0_state_contract": "0x6A0-SEAT_INFO-plus-0x6A2-to-0x302",
|
||||
"can0_control_id": "0x6A0",
|
||||
"can0_control_opcodes": ["0x63", "0x64", "0x65", "0x66"],
|
||||
"can0_forbidden_workflows": [
|
||||
"flash",
|
||||
"bootloader_entry",
|
||||
"test",
|
||||
"calibration",
|
||||
"homing",
|
||||
],
|
||||
"approval_required": self.approval_required,
|
||||
"approval_configured": bool(self.approval_token),
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user