添加无线模块

This commit is contained in:
minghanchen
2026-08-19 09:59:36 +08:00
parent 344f662970
commit 52c4dd08f9
33 changed files with 4740 additions and 246 deletions
@@ -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),
}